prmd 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/Gemfile.lock +3 -1
- data/Rakefile +1 -1
- data/bin/prmd +6 -3
- data/docs/schemata.md +4 -2
- data/lib/prmd.rb +1 -0
- data/lib/prmd/commands/combine.rb +12 -2
- data/lib/prmd/commands/render.rb +9 -1
- data/lib/prmd/commands/verify.rb +39 -75
- data/lib/prmd/schema.rb +8 -4
- data/lib/prmd/template.rb +19 -0
- data/lib/prmd/templates/schema.erb +13 -8
- data/lib/prmd/templates/schemata.erb +17 -146
- data/lib/prmd/templates/schemata/helper.erb +80 -0
- data/lib/prmd/templates/schemata/link.erb +58 -0
- data/lib/prmd/templates/schemata/link_curl_example.erb +25 -0
- data/lib/prmd/version.rb +1 -1
- data/prmd.gemspec +1 -0
- data/schemas/hyper-schema.json +168 -0
- data/schemas/interagent-hyper-schema.json +175 -0
- data/schemas/schema.json +150 -0
- data/test/commands/verify_test.rb +187 -0
- data/test/helpers.rb +1 -0
- metadata +25 -2
@@ -0,0 +1,80 @@
|
|
1
|
+
<%-
|
2
|
+
def extract_attributes(schema, properties)
|
3
|
+
attributes = []
|
4
|
+
properties = properties.sort_by {|k,v| k} # ensure consistent ordering
|
5
|
+
|
6
|
+
properties.each do |key, value|
|
7
|
+
# found a reference to another element:
|
8
|
+
_, value = schema.dereference(value)
|
9
|
+
if value.has_key?('anyOf')
|
10
|
+
descriptions = []
|
11
|
+
examples = []
|
12
|
+
|
13
|
+
# sort anyOf! always show unique identifier first
|
14
|
+
anyof = value['anyOf'].sort_by do |property|
|
15
|
+
property['$ref'].split('/').last.gsub('id', 'a')
|
16
|
+
end
|
17
|
+
|
18
|
+
anyof.each do |ref|
|
19
|
+
_, nested_field = schema.dereference(ref)
|
20
|
+
descriptions << nested_field['description']
|
21
|
+
examples << nested_field['example']
|
22
|
+
end
|
23
|
+
|
24
|
+
# avoid repetition :}
|
25
|
+
description = if descriptions.size > 1
|
26
|
+
descriptions.first.gsub!(/ of (this )?.*/, "")
|
27
|
+
descriptions[1..-1].map { |d| d.gsub!(/unique /, "") }
|
28
|
+
[descriptions[0...-1].join(", "), descriptions.last].join(" or ")
|
29
|
+
else
|
30
|
+
description = descriptions.last
|
31
|
+
end
|
32
|
+
|
33
|
+
example = [*examples].map { |e| "<code>#{e.to_json}</code>" }.join(" or ")
|
34
|
+
attributes << [key, "string", description, example]
|
35
|
+
|
36
|
+
# found a nested object
|
37
|
+
elsif value['type'] == ['object'] && value['properties']
|
38
|
+
nested = extract_attributes(schema, value['properties'])
|
39
|
+
nested.each do |attribute|
|
40
|
+
attribute[0] = "#{key}:#{attribute[0]}"
|
41
|
+
end
|
42
|
+
attributes.concat(nested)
|
43
|
+
# just a regular attribute
|
44
|
+
else
|
45
|
+
description = value['description']
|
46
|
+
if value['enum']
|
47
|
+
description += '<br/><b>one of:</b>' + [*value['enum']].map { |e| "<code>#{e.to_json}</code>" }.join(" or ")
|
48
|
+
end
|
49
|
+
|
50
|
+
if value.has_key?('example')
|
51
|
+
example = [*value['example']].map { |e| "<code>#{e.to_json}</code>" }.join(" or ")
|
52
|
+
elsif value['type'] == ['array'] && value.has_key?('items')
|
53
|
+
example = "<code>#{schema.schema_value_example(value)}</code>"
|
54
|
+
end
|
55
|
+
|
56
|
+
type = if value['type'].include?('null')
|
57
|
+
'nullable '
|
58
|
+
else
|
59
|
+
''
|
60
|
+
end
|
61
|
+
type += (value['format'] || (value['type'] - ['null']).first)
|
62
|
+
attributes << [key, type, description, example]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
return attributes
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_link_path(schema, link)
|
69
|
+
link['href'].gsub(%r|(\{\([^\)]+\)\})|) do |ref|
|
70
|
+
ref = ref.gsub('%2F', '/').gsub('%23', '#').gsub(%r|[\{\(\)\}]|, '')
|
71
|
+
ref_resource = ref.split('#/definitions/').last.split('/').first.gsub('-','_')
|
72
|
+
identity_key, identity_value = schema.dereference(ref)
|
73
|
+
if identity_value.has_key?('anyOf')
|
74
|
+
'{' + ref_resource + '_' + identity_value['anyOf'].map {|r| r['$ref'].split('/').last}.join('_or_') + '}'
|
75
|
+
else
|
76
|
+
'{' + ref_resource + '_' + identity_key.split('/').last + '}'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
%>
|
@@ -0,0 +1,58 @@
|
|
1
|
+
<%-
|
2
|
+
path = build_link_path(schema, link)
|
3
|
+
link_schema_properties_template = Prmd::Template::load('link_schema_properties.erb', options[:template])
|
4
|
+
-%>
|
5
|
+
### <%= title %> <%= link['title'] %>
|
6
|
+
<%= link['description'] %>
|
7
|
+
|
8
|
+
```
|
9
|
+
<%= link['method'] %> <%= path %>
|
10
|
+
```
|
11
|
+
|
12
|
+
<%- if link.has_key?('schema') && link['schema'].has_key?('properties') %>
|
13
|
+
<%-
|
14
|
+
required, optional = link['schema']['properties'].partition do |k, v|
|
15
|
+
(link['schema']['required'] || []).include?(k)
|
16
|
+
end.map { |partition| Hash[partition] }
|
17
|
+
%>
|
18
|
+
<%- unless required.empty? %>
|
19
|
+
#### Required Parameters
|
20
|
+
<%= Erubis::Eruby.new(link_schema_properties_template).result(params: required, schema: schema) %>
|
21
|
+
|
22
|
+
<%- end %>
|
23
|
+
<%- unless optional.empty? %>
|
24
|
+
#### Optional Parameters
|
25
|
+
<%= Erubis::Eruby.new(link_schema_properties_template).result(params: optional, schema: schema) %>
|
26
|
+
<%- end %>
|
27
|
+
<%- end %>
|
28
|
+
|
29
|
+
#### Curl Example
|
30
|
+
<%=
|
31
|
+
Prmd::Template::render(File.join('schemata', 'link_curl_example.erb'), File.dirname(options[:template]), {
|
32
|
+
options: options,
|
33
|
+
resource: resource,
|
34
|
+
schema: schema,
|
35
|
+
schemata: schemata,
|
36
|
+
link: link,
|
37
|
+
path: path
|
38
|
+
})
|
39
|
+
%>
|
40
|
+
|
41
|
+
#### Response Example
|
42
|
+
```
|
43
|
+
HTTP/1.1 <%= case link['rel']
|
44
|
+
when 'create'
|
45
|
+
'201 Created'
|
46
|
+
else
|
47
|
+
'200 OK'
|
48
|
+
end %>
|
49
|
+
```
|
50
|
+
```json
|
51
|
+
<%- if link.has_key?('targetSchema') %>
|
52
|
+
<%= JSON.pretty_generate(schema.schema_example(link['targetSchema'])) %>
|
53
|
+
<%- elsif link['rel'] == 'instances' %>
|
54
|
+
<%= JSON.pretty_generate([schema.schemata_example(resource)]) %>
|
55
|
+
<%- else %>
|
56
|
+
<%= JSON.pretty_generate(schema.schemata_example(resource)) %>
|
57
|
+
<%- end %>
|
58
|
+
```
|
@@ -0,0 +1,25 @@
|
|
1
|
+
```term
|
2
|
+
<%-
|
3
|
+
data = {}
|
4
|
+
path = path.gsub(/{([^}]*)}/) {|match| '$' + match.gsub(/[{}]/, '').upcase}
|
5
|
+
|
6
|
+
if link.has_key?('schema')
|
7
|
+
data.merge!(schema.schema_example(link['schema']))
|
8
|
+
|
9
|
+
if link['method'].upcase == 'GET' && !data.empty?
|
10
|
+
path << '?'
|
11
|
+
data.sort_by {|k,_| k.to_s }.each do |key, values|
|
12
|
+
[values].flatten.each do |value|
|
13
|
+
path << [key.to_s, CGI.escape(value.to_s)].join('=') << '&'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
path.chop! # remove trailing '&'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
%>
|
20
|
+
$ curl -n -X <%= link['method'] %> <%= schema.href %><%= path %>
|
21
|
+
<%- unless data.empty? || link['method'].upcase == 'GET' %>
|
22
|
+
-H "Content-Type: <%= options[:content_type] %>" \
|
23
|
+
-d '<%= data.to_json %>'
|
24
|
+
<%- end %>
|
25
|
+
```
|
data/lib/prmd/version.rb
CHANGED
data/prmd.gemspec
CHANGED
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "erubis", "~> 2.7"
|
22
|
+
spec.add_dependency "json_schema", "~> 0.1"
|
22
23
|
|
23
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
24
25
|
spec.add_development_dependency "rake", "~> 10.2"
|
@@ -0,0 +1,168 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-04/hyper-schema#",
|
3
|
+
"id": "http://json-schema.org/draft-04/hyper-schema#",
|
4
|
+
"title": "JSON Hyper-Schema",
|
5
|
+
"allOf": [
|
6
|
+
{
|
7
|
+
"$ref": "http://json-schema.org/draft-04/schema#"
|
8
|
+
}
|
9
|
+
],
|
10
|
+
"properties": {
|
11
|
+
"additionalItems": {
|
12
|
+
"anyOf": [
|
13
|
+
{
|
14
|
+
"type": "boolean"
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"$ref": "#"
|
18
|
+
}
|
19
|
+
]
|
20
|
+
},
|
21
|
+
"additionalProperties": {
|
22
|
+
"anyOf": [
|
23
|
+
{
|
24
|
+
"type": "boolean"
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"$ref": "#"
|
28
|
+
}
|
29
|
+
]
|
30
|
+
},
|
31
|
+
"dependencies": {
|
32
|
+
"additionalProperties": {
|
33
|
+
"anyOf": [
|
34
|
+
{
|
35
|
+
"$ref": "#"
|
36
|
+
},
|
37
|
+
{
|
38
|
+
"type": "array"
|
39
|
+
}
|
40
|
+
]
|
41
|
+
}
|
42
|
+
},
|
43
|
+
"items": {
|
44
|
+
"anyOf": [
|
45
|
+
{
|
46
|
+
"$ref": "#"
|
47
|
+
},
|
48
|
+
{
|
49
|
+
"$ref": "#/definitions/schemaArray"
|
50
|
+
}
|
51
|
+
]
|
52
|
+
},
|
53
|
+
"definitions": {
|
54
|
+
"additionalProperties": {
|
55
|
+
"$ref": "#"
|
56
|
+
}
|
57
|
+
},
|
58
|
+
"patternProperties": {
|
59
|
+
"additionalProperties": {
|
60
|
+
"$ref": "#"
|
61
|
+
}
|
62
|
+
},
|
63
|
+
"properties": {
|
64
|
+
"additionalProperties": {
|
65
|
+
"$ref": "#"
|
66
|
+
}
|
67
|
+
},
|
68
|
+
"allOf": {
|
69
|
+
"$ref": "#/definitions/schemaArray"
|
70
|
+
},
|
71
|
+
"anyOf": {
|
72
|
+
"$ref": "#/definitions/schemaArray"
|
73
|
+
},
|
74
|
+
"oneOf": {
|
75
|
+
"$ref": "#/definitions/schemaArray"
|
76
|
+
},
|
77
|
+
"not": {
|
78
|
+
"$ref": "#"
|
79
|
+
},
|
80
|
+
|
81
|
+
"links": {
|
82
|
+
"type": "array",
|
83
|
+
"items": {
|
84
|
+
"$ref": "#/definitions/linkDescription"
|
85
|
+
}
|
86
|
+
},
|
87
|
+
"fragmentResolution": {
|
88
|
+
"type": "string"
|
89
|
+
},
|
90
|
+
"media": {
|
91
|
+
"type": "object",
|
92
|
+
"properties": {
|
93
|
+
"type": {
|
94
|
+
"description": "A media type, as described in RFC 2046",
|
95
|
+
"type": "string"
|
96
|
+
},
|
97
|
+
"binaryEncoding": {
|
98
|
+
"description": "A content encoding scheme, as described in RFC 2045",
|
99
|
+
"type": "string"
|
100
|
+
}
|
101
|
+
}
|
102
|
+
},
|
103
|
+
"pathStart": {
|
104
|
+
"description": "Instances' URIs must start with this value for this schema to apply to them",
|
105
|
+
"type": "string",
|
106
|
+
"format": "uri"
|
107
|
+
}
|
108
|
+
},
|
109
|
+
"definitions": {
|
110
|
+
"schemaArray": {
|
111
|
+
"type": "array",
|
112
|
+
"items": {
|
113
|
+
"$ref": "#"
|
114
|
+
}
|
115
|
+
},
|
116
|
+
"linkDescription": {
|
117
|
+
"title": "Link Description Object",
|
118
|
+
"type": "object",
|
119
|
+
"required": [ "href", "rel" ],
|
120
|
+
"properties": {
|
121
|
+
"href": {
|
122
|
+
"description": "a URI template, as defined by RFC 6570, with the addition of the $, ( and ) characters for pre-processing",
|
123
|
+
"type": "string"
|
124
|
+
},
|
125
|
+
"rel": {
|
126
|
+
"description": "relation to the target resource of the link",
|
127
|
+
"type": "string"
|
128
|
+
},
|
129
|
+
"title": {
|
130
|
+
"description": "a title for the link",
|
131
|
+
"type": "string"
|
132
|
+
},
|
133
|
+
"targetSchema": {
|
134
|
+
"description": "JSON Schema describing the link target",
|
135
|
+
"$ref": "#"
|
136
|
+
},
|
137
|
+
"mediaType": {
|
138
|
+
"description": "media type (as defined by RFC 2046) describing the link target",
|
139
|
+
"type": "string"
|
140
|
+
},
|
141
|
+
"method": {
|
142
|
+
"description": "method for requesting the target of the link (e.g. for HTTP this might be \"GET\" or \"DELETE\")",
|
143
|
+
"type": "string"
|
144
|
+
},
|
145
|
+
"encType": {
|
146
|
+
"description": "The media type in which to submit data along with the request",
|
147
|
+
"type": "string",
|
148
|
+
"default": "application/json"
|
149
|
+
},
|
150
|
+
"schema": {
|
151
|
+
"description": "Schema describing the data to submit along with the request",
|
152
|
+
"$ref": "#"
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
},
|
157
|
+
"links": [
|
158
|
+
{
|
159
|
+
"rel": "self",
|
160
|
+
"href": "{+id}"
|
161
|
+
},
|
162
|
+
{
|
163
|
+
"rel": "full",
|
164
|
+
"href": "{+($ref)}"
|
165
|
+
}
|
166
|
+
]
|
167
|
+
}
|
168
|
+
|
@@ -0,0 +1,175 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://interagent.github.io/interagent-hyper-schema#",
|
3
|
+
"id": "http://interagent.github.io/interagent-hyper-schema#",
|
4
|
+
"title": "Heroku JSON Hyper-Schema",
|
5
|
+
"allOf": [
|
6
|
+
{
|
7
|
+
"$ref": "http://json-schema.org/draft-04/hyper-schema#"
|
8
|
+
}
|
9
|
+
],
|
10
|
+
"definitions": {
|
11
|
+
"identity": {
|
12
|
+
"anyOf": [
|
13
|
+
{
|
14
|
+
"additionalProperties": false,
|
15
|
+
"properties": {
|
16
|
+
"anyOf": {
|
17
|
+
"additionalProperties": {
|
18
|
+
"$ref": "#/definitions/ref"
|
19
|
+
},
|
20
|
+
"minProperties": 1
|
21
|
+
}
|
22
|
+
},
|
23
|
+
"required": ["anyOf"]
|
24
|
+
},
|
25
|
+
{
|
26
|
+
"properties": {},
|
27
|
+
"strictProperties": true
|
28
|
+
}
|
29
|
+
]
|
30
|
+
},
|
31
|
+
"ref": {
|
32
|
+
"additionalProperties": false,
|
33
|
+
"properties": {
|
34
|
+
"$ref": {
|
35
|
+
"type": "string"
|
36
|
+
}
|
37
|
+
},
|
38
|
+
"required": ["$ref"]
|
39
|
+
},
|
40
|
+
"resource": {
|
41
|
+
"dependencies": {
|
42
|
+
"properties": {
|
43
|
+
"properties": {
|
44
|
+
"definitions": {
|
45
|
+
"additionalProperties": {
|
46
|
+
"$ref": "#/definitions/resourceDefinition"
|
47
|
+
},
|
48
|
+
"properties": {
|
49
|
+
"identity": {
|
50
|
+
"$ref": "#/definitions/identity"
|
51
|
+
}
|
52
|
+
},
|
53
|
+
"required": ["identity"]
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
},
|
58
|
+
"properties": {
|
59
|
+
"links": {
|
60
|
+
"items": {
|
61
|
+
"$ref": "#/definitions/resourceLink"
|
62
|
+
}
|
63
|
+
},
|
64
|
+
"properties": {
|
65
|
+
"patternProperties": {
|
66
|
+
"^[a-z][a-z_]*[a-z]$": {
|
67
|
+
"$ref": "#/definitions/resourceProperty"
|
68
|
+
}
|
69
|
+
},
|
70
|
+
"strictProperties": true
|
71
|
+
},
|
72
|
+
"strictProperties": {
|
73
|
+
"enum": [true],
|
74
|
+
"type": "boolean"
|
75
|
+
}
|
76
|
+
},
|
77
|
+
"required": [
|
78
|
+
"definitions",
|
79
|
+
"description",
|
80
|
+
"links",
|
81
|
+
"title",
|
82
|
+
"type"
|
83
|
+
]
|
84
|
+
},
|
85
|
+
"resourceDefinition": {
|
86
|
+
"anyOf": [
|
87
|
+
{
|
88
|
+
"required": ["example", "type"]
|
89
|
+
},
|
90
|
+
{
|
91
|
+
"required": ["type"],
|
92
|
+
"type": ["array"]
|
93
|
+
},
|
94
|
+
{
|
95
|
+
"required": ["type"],
|
96
|
+
"type": ["object"]
|
97
|
+
}
|
98
|
+
],
|
99
|
+
"not": {
|
100
|
+
"required": ["links"]
|
101
|
+
},
|
102
|
+
"required": ["description"]
|
103
|
+
},
|
104
|
+
"resourceLink": {
|
105
|
+
"properties": {
|
106
|
+
"href": {
|
107
|
+
"pattern": "^(/([a-z][a-z\\-]*[a-z]|\\{\\(.*\\)\\}))+$"
|
108
|
+
},
|
109
|
+
"schema": {
|
110
|
+
"anyOf": [
|
111
|
+
{
|
112
|
+
"required": ["properties"]
|
113
|
+
},
|
114
|
+
{
|
115
|
+
"required": ["patternProperties"]
|
116
|
+
}
|
117
|
+
],
|
118
|
+
"required": ["type"]
|
119
|
+
}
|
120
|
+
},
|
121
|
+
"required": ["description", "href", "method", "rel", "title"]
|
122
|
+
},
|
123
|
+
"resourceProperty": {
|
124
|
+
"anyOf": [
|
125
|
+
{
|
126
|
+
"$ref": "#/definitions/ref"
|
127
|
+
},
|
128
|
+
{
|
129
|
+
"properties": {
|
130
|
+
"properties": {
|
131
|
+
"additionalProperties": {
|
132
|
+
"$ref": "#/definitions/resourceProperty"
|
133
|
+
}
|
134
|
+
},
|
135
|
+
"strictProperties": {
|
136
|
+
"enum": [true],
|
137
|
+
"type": "boolean"
|
138
|
+
}
|
139
|
+
},
|
140
|
+
"required": ["description", "properties", "type"]
|
141
|
+
},
|
142
|
+
{
|
143
|
+
"properties": {
|
144
|
+
"items": {
|
145
|
+
"$ref": "#/definitions/ref"
|
146
|
+
}
|
147
|
+
},
|
148
|
+
"required": ["description", "items", "type"]
|
149
|
+
}
|
150
|
+
]
|
151
|
+
}
|
152
|
+
},
|
153
|
+
"properties": {
|
154
|
+
"definitions": {
|
155
|
+
"additionalProperties": {
|
156
|
+
"$ref": "#/definitions/resource"
|
157
|
+
}
|
158
|
+
},
|
159
|
+
"properties": {
|
160
|
+
"additionalProperties": {
|
161
|
+
"$ref": "#/definitions/ref"
|
162
|
+
}
|
163
|
+
}
|
164
|
+
},
|
165
|
+
"required": [
|
166
|
+
"$schema",
|
167
|
+
"definitions",
|
168
|
+
"description",
|
169
|
+
"id",
|
170
|
+
"links",
|
171
|
+
"properties",
|
172
|
+
"title",
|
173
|
+
"type"
|
174
|
+
]
|
175
|
+
}
|