jdoc 0.0.1
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 +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +9 -0
- data/Rakefile +2 -0
- data/bin/jdoc +19 -0
- data/example-api-documentation.md +125 -0
- data/jdoc.gemspec +28 -0
- data/lib/jdoc.rb +12 -0
- data/lib/jdoc/generator.rb +45 -0
- data/lib/jdoc/link.rb +150 -0
- data/lib/jdoc/property.rb +59 -0
- data/lib/jdoc/resource.rb +55 -0
- data/lib/jdoc/schema.rb +35 -0
- data/lib/jdoc/version.rb +3 -0
- data/spec/fixtures/schema.yml +80 -0
- data/spec/jdoc/generator_spec.rb +149 -0
- data/spec/spec_helper.rb +7 -0
- data/template.md.erb +40 -0
- metadata +194 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d6199475826180211b7537e2b602a2fc83defb0e
|
4
|
+
data.tar.gz: 60b7fb0b6bd29b358c7d8962156cabb0bd528cd3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4d3fbe63ce3402525682b739a24bab0ff0b35fbfd8ae8339502a23a22311ca9d15d819da570d5813e1bb237923060d5f73b231529e28fc46b2048f2397e19c85
|
7
|
+
data.tar.gz: 89abc59441bdc661a499a3e87f4484b50e6268db4162fcbe86898d4d6bba0b34b3935932f937ca3ea3475114f23e85f84c4ab9712a6d064b9101607b1a013a85
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Ryo Nakamura
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
data/bin/jdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
3
|
+
require "jdoc"
|
4
|
+
require "yaml"
|
5
|
+
|
6
|
+
unless path = ARGV[0]
|
7
|
+
puts "usage: #{$0} <schema.json or schema.yml>"
|
8
|
+
exit
|
9
|
+
end
|
10
|
+
|
11
|
+
case File.extname(path)
|
12
|
+
when ".yml", ".yaml"
|
13
|
+
schema = YAML.load_file(path)
|
14
|
+
else
|
15
|
+
str = File.read(path)
|
16
|
+
schema = MultiJson.decode(str)
|
17
|
+
end
|
18
|
+
|
19
|
+
puts Jdoc::Generator.call(schema)
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# Example API
|
2
|
+
* [App](#app)
|
3
|
+
* [GET /apps](#get-apps)
|
4
|
+
* [POST /apps](#post-apps)
|
5
|
+
* [GET /apps/:id](#get-appsid)
|
6
|
+
* [PATCH /apps/:id](#patch-appsid)
|
7
|
+
* [DELETE /apps/:id](#delete-appsid)
|
8
|
+
|
9
|
+
## App
|
10
|
+
An app is a program to be deployed.
|
11
|
+
|
12
|
+
### Properties
|
13
|
+
* id - unique identifier of app
|
14
|
+
* Example: `01234567-89ab-cdef-0123-456789abcdef`
|
15
|
+
* Type: string
|
16
|
+
* Format: uuid
|
17
|
+
* ReadOnly: true
|
18
|
+
* name - unique name of app
|
19
|
+
* Example: `example`
|
20
|
+
* Type: string
|
21
|
+
* Patern: `(?-mix:^[a-z][a-z0-9-]{3,50}$)`
|
22
|
+
|
23
|
+
### GET /apps
|
24
|
+
List existing apps.
|
25
|
+
|
26
|
+
```
|
27
|
+
GET /apps HTTP/1.1
|
28
|
+
Content-Type: application/json
|
29
|
+
Host: api.example.com
|
30
|
+
```
|
31
|
+
|
32
|
+
```
|
33
|
+
HTTP/1.1 200
|
34
|
+
Content-Type: application/json
|
35
|
+
|
36
|
+
{
|
37
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
38
|
+
"name": "example"
|
39
|
+
}
|
40
|
+
```
|
41
|
+
|
42
|
+
### POST /apps
|
43
|
+
Create a new app.
|
44
|
+
|
45
|
+
```
|
46
|
+
POST /apps HTTP/1.1
|
47
|
+
Content-Type: application/json
|
48
|
+
Host: api.example.com
|
49
|
+
|
50
|
+
{
|
51
|
+
"name": "example"
|
52
|
+
}
|
53
|
+
```
|
54
|
+
|
55
|
+
```
|
56
|
+
HTTP/1.1 201
|
57
|
+
Content-Type: application/json
|
58
|
+
|
59
|
+
{
|
60
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
61
|
+
"name": "example"
|
62
|
+
}
|
63
|
+
```
|
64
|
+
|
65
|
+
### GET /apps/:id
|
66
|
+
Info for existing app.
|
67
|
+
|
68
|
+
```
|
69
|
+
GET /apps/:id HTTP/1.1
|
70
|
+
Content-Type: application/json
|
71
|
+
Host: api.example.com
|
72
|
+
```
|
73
|
+
|
74
|
+
```
|
75
|
+
HTTP/1.1 200
|
76
|
+
Content-Type: application/json
|
77
|
+
|
78
|
+
{
|
79
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
80
|
+
"name": "example"
|
81
|
+
}
|
82
|
+
```
|
83
|
+
|
84
|
+
### PATCH /apps/:id
|
85
|
+
Update an existing app.
|
86
|
+
|
87
|
+
```
|
88
|
+
PATCH /apps/:id HTTP/1.1
|
89
|
+
Content-Type: application/json
|
90
|
+
Host: api.example.com
|
91
|
+
|
92
|
+
{
|
93
|
+
"name": "example"
|
94
|
+
}
|
95
|
+
```
|
96
|
+
|
97
|
+
```
|
98
|
+
HTTP/1.1 200
|
99
|
+
Content-Type: application/json
|
100
|
+
|
101
|
+
{
|
102
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
103
|
+
"name": "example"
|
104
|
+
}
|
105
|
+
```
|
106
|
+
|
107
|
+
### DELETE /apps/:id
|
108
|
+
Delete an existing app.
|
109
|
+
|
110
|
+
```
|
111
|
+
DELETE /apps/:id HTTP/1.1
|
112
|
+
Content-Type: application/json
|
113
|
+
Host: api.example.com
|
114
|
+
```
|
115
|
+
|
116
|
+
```
|
117
|
+
HTTP/1.1 200
|
118
|
+
Content-Type: application/json
|
119
|
+
|
120
|
+
{
|
121
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
122
|
+
"name": "example"
|
123
|
+
}
|
124
|
+
```
|
125
|
+
|
data/jdoc.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "jdoc/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "jdoc"
|
7
|
+
spec.version = Jdoc::VERSION
|
8
|
+
spec.authors = ["Ryo Nakamura"]
|
9
|
+
spec.email = ["r7kamura@gmail.com"]
|
10
|
+
spec.summary = "Generate API documentation from JSON Schema."
|
11
|
+
spec.homepage = "https://github.com/r7kamura/jdoc"
|
12
|
+
spec.license = "MIT"
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_dependency "multi_json"
|
20
|
+
spec.add_dependency "rack-spec", ">= 0.1.7"
|
21
|
+
spec.add_development_dependency "activesupport"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
23
|
+
spec.add_development_dependency "erubis"
|
24
|
+
spec.add_development_dependency "json_schema"
|
25
|
+
spec.add_development_dependency "pry"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec", "2.14.1"
|
28
|
+
end
|
data/lib/jdoc.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Jdoc
|
2
|
+
class Generator
|
3
|
+
TEMPLATE_PATH = File.expand_path("../../../template.md.erb", __FILE__)
|
4
|
+
|
5
|
+
# Utility wrapper for Jdoc::Generator#call
|
6
|
+
# @return [String]
|
7
|
+
def self.call(*args)
|
8
|
+
new(*args).call
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param schema [Hash] JSON Schema represented as a Hash
|
12
|
+
def initialize(schema)
|
13
|
+
@raw_schema = schema
|
14
|
+
end
|
15
|
+
|
16
|
+
# Generates documentation in Markdown format from JSON schema
|
17
|
+
# @return [String] Text of generated markdown document
|
18
|
+
def call
|
19
|
+
eruby.result(context)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @return [Hash] Context object used as a set of local variables for rendering template
|
25
|
+
def context
|
26
|
+
{ schema: schema }
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Jdoc::Schema]
|
30
|
+
# @raise [JsonSchema::SchemaError] Raises if given invalid JSON Schema
|
31
|
+
def schema
|
32
|
+
@schema ||= Jdoc::Schema.new(@raw_schema)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Erubis::Eruby]
|
36
|
+
def eruby
|
37
|
+
Erubis::Eruby.new(template)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [String] ERB template
|
41
|
+
def template
|
42
|
+
File.read(TEMPLATE_PATH)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/jdoc/link.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
module Jdoc
|
2
|
+
class Link
|
3
|
+
# @param link [JsonSchema::Schema::Link]
|
4
|
+
def initialize(link: nil)
|
5
|
+
@raw_link = link
|
6
|
+
end
|
7
|
+
|
8
|
+
# @return [String] method + path
|
9
|
+
# @example
|
10
|
+
# link.endpoint #=> "GET /apps"
|
11
|
+
def endpoint
|
12
|
+
"#{method} #{path}"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Responds to .sort method
|
16
|
+
# @return [Fixnum]
|
17
|
+
def <=>(schema)
|
18
|
+
sort_key <=> schema.sort_key
|
19
|
+
end
|
20
|
+
|
21
|
+
# For #<=> method
|
22
|
+
# @return [String]
|
23
|
+
def sort_key
|
24
|
+
"#{path} #{method_order_score}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String] Description for this endpoint, defined in description property
|
28
|
+
# @example
|
29
|
+
# link.description #=> "List existing apps."
|
30
|
+
def description
|
31
|
+
@raw_link.description
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String] Href anchor for putting link in ToC
|
35
|
+
# @example
|
36
|
+
# link.anchor #=> "#get-apps"
|
37
|
+
def anchor
|
38
|
+
"#" + endpoint.gsub(" ", "-").gsub(/[:\/]/, "").downcase
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String] Markdown styled link text for endpoint
|
42
|
+
# @example
|
43
|
+
# link.hyperlink #=> "[GET /apps](#get-apps)"
|
44
|
+
def hyperlink
|
45
|
+
"[#{endpoint}](#{anchor})"
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [String] Upper cased HTTP request method name
|
49
|
+
# @example
|
50
|
+
# link.method #=> "GET"
|
51
|
+
def method
|
52
|
+
@method ||= @raw_link.method.to_s.upcase
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [String] Request path name, defined at href property
|
56
|
+
# @note URI Template is replaced with placeholder
|
57
|
+
# @example
|
58
|
+
# link.path #=> "GET /apps/:id"
|
59
|
+
def path
|
60
|
+
@path ||= @raw_link.href.gsub(/{(.+)}/) do |matched|
|
61
|
+
":" + CGI.unescape($1).gsub(/[()]/, "").split("/").last
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [String, nil] Example request body in JSON format
|
66
|
+
def request_body
|
67
|
+
MultiJson.encode(RequestBodyGenerator.call(schema), pretty: true) + "\n"
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [true, false] True if this endpoint must have request body
|
71
|
+
def has_request_body?
|
72
|
+
["PATCH", "POST", "PUT"].include?(method)
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [String] JSON response body generated from example properties
|
76
|
+
def response_body
|
77
|
+
MultiJson.encode(response_hash, pretty: true)
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [Fixnum] Preferred respone status code for this endpoint
|
81
|
+
def response_status
|
82
|
+
method == "POST" ? 201 : 200
|
83
|
+
end
|
84
|
+
|
85
|
+
# @return [JsonSchema::Schema] Schema for this link, specified by targetSchema or parent schema
|
86
|
+
def schema
|
87
|
+
@raw_link.target_schema || @raw_link.parent
|
88
|
+
end
|
89
|
+
|
90
|
+
# @return [Json::Link::Resource]
|
91
|
+
# @note Resource means each property of top-level properties in this context
|
92
|
+
def resource
|
93
|
+
@resource ||= Resource.new(schema)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
# @return [Hash]
|
99
|
+
# @raise [Rack::Spec::Mock::ExampleNotFound]
|
100
|
+
def response_hash
|
101
|
+
Rack::Spec::Mock::ResponseGenerator.call(schema)
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [Fixnum] Order score, used to sort links by preferred method order
|
105
|
+
def method_order_score
|
106
|
+
case method
|
107
|
+
when "GET"
|
108
|
+
1
|
109
|
+
when "POST"
|
110
|
+
2
|
111
|
+
when "PUT"
|
112
|
+
3
|
113
|
+
when "PATCH"
|
114
|
+
4
|
115
|
+
when "DELETE"
|
116
|
+
5
|
117
|
+
else
|
118
|
+
6
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class RequestBodyGenerator
|
123
|
+
# Generates example request body from given schema
|
124
|
+
# @note Not includes properties that have readOnly property
|
125
|
+
# @return [Hash]
|
126
|
+
# @example
|
127
|
+
# Jdoc::Link::RequestBodyGenerator(schema) #=> { "name" => "example", "description" => "foo bar." }
|
128
|
+
def self.call(schema)
|
129
|
+
schema.properties.inject({}) do |result, (key, value)|
|
130
|
+
if value.data["readOnly"]
|
131
|
+
result
|
132
|
+
else
|
133
|
+
result.merge(
|
134
|
+
key => case
|
135
|
+
when !value.properties.empty?
|
136
|
+
call(value)
|
137
|
+
when !value.data["example"].nil?
|
138
|
+
value.data["example"]
|
139
|
+
when value.type.include?("null")
|
140
|
+
nil
|
141
|
+
else
|
142
|
+
raise ExampleNotFound, "No example found for #{schema.pointer}/#{key}"
|
143
|
+
end
|
144
|
+
)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Jdoc
|
2
|
+
class Property
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
# @param name [String]
|
6
|
+
# @param schema [JsonSchema::Schema]
|
7
|
+
def initialize(name: nil, schema: nil)
|
8
|
+
@name = name
|
9
|
+
@schema = schema
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Hash] Key-Value pair of metadata of this property
|
13
|
+
def options
|
14
|
+
{
|
15
|
+
Example: example,
|
16
|
+
Type: type,
|
17
|
+
Format: format,
|
18
|
+
Patern: pattern,
|
19
|
+
ReadOnly: read_only,
|
20
|
+
}.reject {|key, value| value.nil? }
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String, nil] Description text, defined in description property
|
24
|
+
def description
|
25
|
+
@schema.description
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [String, nil] Example value, defined in example property
|
29
|
+
def example
|
30
|
+
if str = @schema.data["example"]
|
31
|
+
"`#{str}`"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String, nil] Pattern constraint, defined in pattern property
|
36
|
+
def pattern
|
37
|
+
if str = @schema.pattern
|
38
|
+
"`#{str}`"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Stirng, nil] Format constraint, defined in format property
|
43
|
+
def format
|
44
|
+
@schema.format
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [true, nil] True if readOnly property is defined and it's true
|
48
|
+
def read_only
|
49
|
+
true if @schema.read_only == true
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String, nil] Possible types defined in type property
|
53
|
+
def type
|
54
|
+
if @schema.type
|
55
|
+
@schema.type.join(", ")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Jdoc
|
2
|
+
class Resource
|
3
|
+
attr_reader :schema
|
4
|
+
|
5
|
+
# @param schema [JsonSchema::Schema]
|
6
|
+
def initialize(schema)
|
7
|
+
@schema = schema
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [String] Description for this schema, defined in description property
|
11
|
+
# @example
|
12
|
+
# resource.description #=> "An app is a program to be deployed."
|
13
|
+
def description
|
14
|
+
@schema.description
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String] Href anchor for putting link in ToC
|
18
|
+
# @example
|
19
|
+
# resource.anchor #=> "#app"
|
20
|
+
def anchor
|
21
|
+
"#" + title.gsub(" ", "-").gsub(/[:\/]/, "").downcase
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [String] Markdown styled link text for this resource
|
25
|
+
# @example
|
26
|
+
# resource.hyperlink #=> "[App](#apps)"
|
27
|
+
def hyperlink
|
28
|
+
"[#{title}](#{anchor})"
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String] Title defined in title property
|
32
|
+
# @example
|
33
|
+
# resource.title #=> "App"
|
34
|
+
def title
|
35
|
+
@schema.title
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Array<Jdoc::Property>]
|
39
|
+
def properties
|
40
|
+
@schema.properties.map do |name, schema|
|
41
|
+
Property.new(name: name, schema: schema)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Defined to change uniqueness in Hash key
|
46
|
+
def hash
|
47
|
+
@schema.hash
|
48
|
+
end
|
49
|
+
|
50
|
+
# Defined to change uniqueness in Hash key
|
51
|
+
def eql?(other)
|
52
|
+
@schema.eql?(other.schema)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/jdoc/schema.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Jdoc
|
2
|
+
class Schema
|
3
|
+
# @param schema [Hash] JSON Schema
|
4
|
+
def initialize(schema)
|
5
|
+
@raw_schema = schema
|
6
|
+
end
|
7
|
+
|
8
|
+
# @return [Hash{Jdoc::Schema => Array}] Linkes table indexed by their schemata
|
9
|
+
def links_indexed_by_resource
|
10
|
+
@links_indexed_by_schema ||= links.inject(Hash.new {|h, k| h[k] = [] }) do |result, link|
|
11
|
+
result[link.resource] << link
|
12
|
+
result
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [String, nil] Title property of this schema
|
17
|
+
# @example
|
18
|
+
# schema.title #=> "app"
|
19
|
+
def title
|
20
|
+
@raw_schema["title"]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# @return [Array<Jdoc::Schema::Link>] Sorted links
|
26
|
+
def links
|
27
|
+
rack_schema.links.map {|link| Link.new(link: link) }.sort
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Rack::Spec::Schema]
|
31
|
+
def rack_schema
|
32
|
+
@rack_schema ||= Rack::Spec::Schema.new(@raw_schema)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/jdoc/version.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
---
|
2
|
+
"$schema": http://json-schema.org/draft-04/hyper-schema
|
3
|
+
definitions:
|
4
|
+
app:
|
5
|
+
"$schema": http://json-schema.org/draft-04/hyper-schema
|
6
|
+
description: An app is a program to be deployed.
|
7
|
+
id: schemata/app
|
8
|
+
title: App
|
9
|
+
type:
|
10
|
+
- object
|
11
|
+
required:
|
12
|
+
- id
|
13
|
+
definitions:
|
14
|
+
id:
|
15
|
+
description: unique identifier of app
|
16
|
+
example: 01234567-89ab-cdef-0123-456789abcdef
|
17
|
+
format: uuid
|
18
|
+
readOnly: true
|
19
|
+
type:
|
20
|
+
- string
|
21
|
+
name:
|
22
|
+
description: unique name of app
|
23
|
+
example: example
|
24
|
+
pattern: "^[a-z][a-z0-9-]{3,50}$"
|
25
|
+
readOnly: false
|
26
|
+
type:
|
27
|
+
- string
|
28
|
+
links:
|
29
|
+
- description: Create a new app.
|
30
|
+
href: "/apps"
|
31
|
+
method: POST
|
32
|
+
rel: create
|
33
|
+
schema:
|
34
|
+
properties:
|
35
|
+
name:
|
36
|
+
"$ref": "#/definitions/app/definitions/name"
|
37
|
+
type:
|
38
|
+
- object
|
39
|
+
title: Create
|
40
|
+
- description: Delete an existing app.
|
41
|
+
href: "/apps/{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fid)}"
|
42
|
+
method: DELETE
|
43
|
+
rel: destroy
|
44
|
+
title: Delete
|
45
|
+
- description: Info for existing app.
|
46
|
+
href: "/apps/{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fid)}"
|
47
|
+
method: GET
|
48
|
+
rel: self
|
49
|
+
title: Info
|
50
|
+
- description: List existing apps.
|
51
|
+
href: "/apps"
|
52
|
+
method: GET
|
53
|
+
rel: instances
|
54
|
+
title: List
|
55
|
+
- description: Update an existing app.
|
56
|
+
href: "/apps/{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fid)}"
|
57
|
+
method: PATCH
|
58
|
+
rel: update
|
59
|
+
schema:
|
60
|
+
properties:
|
61
|
+
name:
|
62
|
+
"$ref": "#/definitions/app/definitions/name"
|
63
|
+
type:
|
64
|
+
- object
|
65
|
+
title: Update
|
66
|
+
properties:
|
67
|
+
id:
|
68
|
+
"$ref": "#/definitions/app/definitions/id"
|
69
|
+
name:
|
70
|
+
"$ref": "#/definitions/app/definitions/name"
|
71
|
+
properties:
|
72
|
+
app:
|
73
|
+
"$ref": "#/definitions/app"
|
74
|
+
type:
|
75
|
+
- object
|
76
|
+
description: A schema for a small example API.
|
77
|
+
links:
|
78
|
+
- href: http://localhost:5000
|
79
|
+
rel: self
|
80
|
+
title: Example API
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "active_support/core_ext/string/strip"
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
describe Jdoc::Generator do
|
6
|
+
describe ".call" do
|
7
|
+
subject do
|
8
|
+
described_class.call(schema)
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:schema) do
|
12
|
+
YAML.load_file(schema_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:schema_path) do
|
16
|
+
File.expand_path("../../fixtures/schema.yml", __FILE__)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "returns a String of API documentation in Markdown from given JSON Schema" do
|
20
|
+
should == <<-EOS.strip_heredoc
|
21
|
+
# Example API
|
22
|
+
* [App](#app)
|
23
|
+
* [GET /apps](#get-apps)
|
24
|
+
* [POST /apps](#post-apps)
|
25
|
+
* [GET /apps/:id](#get-appsid)
|
26
|
+
* [PATCH /apps/:id](#patch-appsid)
|
27
|
+
* [DELETE /apps/:id](#delete-appsid)
|
28
|
+
|
29
|
+
## App
|
30
|
+
An app is a program to be deployed.
|
31
|
+
|
32
|
+
### Properties
|
33
|
+
* id - unique identifier of app
|
34
|
+
* Example: `01234567-89ab-cdef-0123-456789abcdef`
|
35
|
+
* Type: string
|
36
|
+
* Format: uuid
|
37
|
+
* ReadOnly: true
|
38
|
+
* name - unique name of app
|
39
|
+
* Example: `example`
|
40
|
+
* Type: string
|
41
|
+
* Patern: `(?-mix:^[a-z][a-z0-9-]{3,50}$)`
|
42
|
+
|
43
|
+
### GET /apps
|
44
|
+
List existing apps.
|
45
|
+
|
46
|
+
```
|
47
|
+
GET /apps HTTP/1.1
|
48
|
+
Content-Type: application/json
|
49
|
+
Host: api.example.com
|
50
|
+
```
|
51
|
+
|
52
|
+
```
|
53
|
+
HTTP/1.1 200
|
54
|
+
Content-Type: application/json
|
55
|
+
|
56
|
+
{
|
57
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
58
|
+
"name": "example"
|
59
|
+
}
|
60
|
+
```
|
61
|
+
|
62
|
+
### POST /apps
|
63
|
+
Create a new app.
|
64
|
+
|
65
|
+
```
|
66
|
+
POST /apps HTTP/1.1
|
67
|
+
Content-Type: application/json
|
68
|
+
Host: api.example.com
|
69
|
+
|
70
|
+
{
|
71
|
+
"name": "example"
|
72
|
+
}
|
73
|
+
```
|
74
|
+
|
75
|
+
```
|
76
|
+
HTTP/1.1 201
|
77
|
+
Content-Type: application/json
|
78
|
+
|
79
|
+
{
|
80
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
81
|
+
"name": "example"
|
82
|
+
}
|
83
|
+
```
|
84
|
+
|
85
|
+
### GET /apps/:id
|
86
|
+
Info for existing app.
|
87
|
+
|
88
|
+
```
|
89
|
+
GET /apps/:id HTTP/1.1
|
90
|
+
Content-Type: application/json
|
91
|
+
Host: api.example.com
|
92
|
+
```
|
93
|
+
|
94
|
+
```
|
95
|
+
HTTP/1.1 200
|
96
|
+
Content-Type: application/json
|
97
|
+
|
98
|
+
{
|
99
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
100
|
+
"name": "example"
|
101
|
+
}
|
102
|
+
```
|
103
|
+
|
104
|
+
### PATCH /apps/:id
|
105
|
+
Update an existing app.
|
106
|
+
|
107
|
+
```
|
108
|
+
PATCH /apps/:id HTTP/1.1
|
109
|
+
Content-Type: application/json
|
110
|
+
Host: api.example.com
|
111
|
+
|
112
|
+
{
|
113
|
+
"name": "example"
|
114
|
+
}
|
115
|
+
```
|
116
|
+
|
117
|
+
```
|
118
|
+
HTTP/1.1 200
|
119
|
+
Content-Type: application/json
|
120
|
+
|
121
|
+
{
|
122
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
123
|
+
"name": "example"
|
124
|
+
}
|
125
|
+
```
|
126
|
+
|
127
|
+
### DELETE /apps/:id
|
128
|
+
Delete an existing app.
|
129
|
+
|
130
|
+
```
|
131
|
+
DELETE /apps/:id HTTP/1.1
|
132
|
+
Content-Type: application/json
|
133
|
+
Host: api.example.com
|
134
|
+
```
|
135
|
+
|
136
|
+
```
|
137
|
+
HTTP/1.1 200
|
138
|
+
Content-Type: application/json
|
139
|
+
|
140
|
+
{
|
141
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
142
|
+
"name": "example"
|
143
|
+
}
|
144
|
+
```
|
145
|
+
|
146
|
+
EOS
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/template.md.erb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# <%= schema.title %>
|
2
|
+
<% schema.links_indexed_by_resource.each do |resource, links| %>
|
3
|
+
* <%= resource.hyperlink %>
|
4
|
+
<% links.each do |link| %>
|
5
|
+
* <%= link.hyperlink %>
|
6
|
+
<% end %>
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
<% schema.links_indexed_by_resource.each do |resource, links| %>
|
10
|
+
## <%= resource.title %>
|
11
|
+
<%= resource.description %>
|
12
|
+
|
13
|
+
### Properties
|
14
|
+
<% resource.properties.each do |property| %>
|
15
|
+
* <%= property.name %> - <%= property.description %>
|
16
|
+
<% property.options.each do |key, value| %>
|
17
|
+
* <%= key %>: <%= value %>
|
18
|
+
<% end %>
|
19
|
+
<% end %>
|
20
|
+
|
21
|
+
<% links.each do |link| %>
|
22
|
+
### <%= link.endpoint %>
|
23
|
+
<%= link.description %>
|
24
|
+
|
25
|
+
```
|
26
|
+
<%= link.method %> <%= link.path %> HTTP/1.1
|
27
|
+
Content-Type: application/json
|
28
|
+
Host: api.example.com
|
29
|
+
<%= "\n" + link.request_body if link.has_request_body? -%>
|
30
|
+
```
|
31
|
+
|
32
|
+
```
|
33
|
+
HTTP/1.1 <%= link.response_status %>
|
34
|
+
Content-Type: application/json
|
35
|
+
|
36
|
+
<%= link.response_body %>
|
37
|
+
```
|
38
|
+
|
39
|
+
<% end %>
|
40
|
+
<% end %>
|
metadata
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jdoc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryo Nakamura
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: multi_json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack-spec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.7
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.7
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.6'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.6'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: erubis
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: json_schema
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 2.14.1
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 2.14.1
|
139
|
+
description:
|
140
|
+
email:
|
141
|
+
- r7kamura@gmail.com
|
142
|
+
executables:
|
143
|
+
- jdoc
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- ".gitignore"
|
148
|
+
- Gemfile
|
149
|
+
- LICENSE.txt
|
150
|
+
- README.md
|
151
|
+
- Rakefile
|
152
|
+
- bin/jdoc
|
153
|
+
- example-api-documentation.md
|
154
|
+
- jdoc.gemspec
|
155
|
+
- lib/jdoc.rb
|
156
|
+
- lib/jdoc/generator.rb
|
157
|
+
- lib/jdoc/link.rb
|
158
|
+
- lib/jdoc/property.rb
|
159
|
+
- lib/jdoc/resource.rb
|
160
|
+
- lib/jdoc/schema.rb
|
161
|
+
- lib/jdoc/version.rb
|
162
|
+
- spec/fixtures/schema.yml
|
163
|
+
- spec/jdoc/generator_spec.rb
|
164
|
+
- spec/spec_helper.rb
|
165
|
+
- template.md.erb
|
166
|
+
homepage: https://github.com/r7kamura/jdoc
|
167
|
+
licenses:
|
168
|
+
- MIT
|
169
|
+
metadata: {}
|
170
|
+
post_install_message:
|
171
|
+
rdoc_options: []
|
172
|
+
require_paths:
|
173
|
+
- lib
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - ">="
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '0'
|
179
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - ">="
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
requirements: []
|
185
|
+
rubyforge_project:
|
186
|
+
rubygems_version: 2.2.2
|
187
|
+
signing_key:
|
188
|
+
specification_version: 4
|
189
|
+
summary: Generate API documentation from JSON Schema.
|
190
|
+
test_files:
|
191
|
+
- spec/fixtures/schema.yml
|
192
|
+
- spec/jdoc/generator_spec.rb
|
193
|
+
- spec/spec_helper.rb
|
194
|
+
has_rdoc:
|