prmd 0.0.1 → 0.1.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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YWJmODRjMTMyNjBmNGE5N2JmZTk4ZmI2MzZiZTM3NDBkZmEyNTI5Yw==
4
+ OTJiYjFkZjYwZTAxYWExOTJmYzAxMWZjYmJiNmQ1YzA3ZTdiMmY0Ng==
5
5
  data.tar.gz: !binary |-
6
- MjJiNjE4ZjA0ODUzNjlhNGZkZmVhZmY3MmRhMzY1MDFkYTM0M2QxMw==
6
+ Y2RmMDI2NTIyNzQxZTk4NjcxZmZhYzU5NTM2NDdlNmM4YWZhMTE4Mg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MThmMTQ3ODhhNDMwNTliZjk4NWY0N2JiOWU3MDk4ZjI1YzE2NDJhNjQ3YjIy
10
- ZGU3OGVjYzBiNjZhNmVhNzljMjY5ZWE0YTJlMjg4NmMyN2JiOWQ1ZDM4N2E0
11
- M2FjNDdjMmNmYzBlZjBiZjk1ZWQ4NWM3MDhiYmVmYTQ3MTYwMDA=
9
+ MTE2N2FhODUzYjgxY2Y0YjJmOGUwYzRkYTQwYmY0Nzg2NDJhNDdjM2JhZGYy
10
+ ZjFjY2MxMGY5ZmNkYjM0OTVlMmUyNTYzMGJlYTdmYjk1MTczOTZiODI3ZjVh
11
+ MWZlYTQ2OTg2MWRlNjg3Yjc4ZmU2MThkMGVjNTc2Yjg5ZGZmMTA=
12
12
  data.tar.gz: !binary |-
13
- ZDMzZWZjMzAyZDYxNjg5NTRmZjRjODJjMDNkOTZmNmNhM2QzNzRjN2M2OGVi
14
- Yjg3NWE1ZWE2Njk5M2Y5ODg4NDQ1NzlmNjZmZTQxY2E4ZWY0YTJmMzI0NGUz
15
- MjM5ZjljNDMxZGNjNGQ3MTI1MmQ3Mjk5MWYyMjM2OGRjMjJkMmI=
13
+ ZjIyMmNlZmI2NGJiMjY2OGE5NWM2ZTJmMzkxMzAyM2NjNTkyOWFmMDZiMTRk
14
+ MWEwOWUwYmY0YjM3Mzk1N2FhNDE1YTdkYWJiNTY1NmUxMTE0NmQ4YjBiOTVm
15
+ Mjk5NzNmODZiNTIwNWUzY2Q3MDIxMjM0ZTRmNTEyMDFjMWU0MWY=
data/.travis.yml CHANGED
@@ -1,2 +1,4 @@
1
1
  language: ruby
2
2
  script: "bundle exec rake"
3
+ notifications:
4
+ email: false
data/Gemfile.lock CHANGED
@@ -2,13 +2,11 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  prmd (0.0.1)
5
- diff-lcs
6
5
  erubis
7
6
 
8
7
  GEM
9
8
  remote: https://rubygems.org/
10
9
  specs:
11
- diff-lcs (1.2.5)
12
10
  erubis (2.7.0)
13
11
  minitest (4.7.5)
14
12
  rake (10.1.0)
data/README.md CHANGED
@@ -1,66 +1,86 @@
1
1
  # Prmd
2
2
 
3
- schema to rule them all
3
+ JSON Schema tooling: scaffold, verify, and generate documentation
4
+ against JSON Schema documents.
5
+
4
6
 
5
7
  ## Introduction
6
8
 
7
- JSON-Schema provides a great way to describe an API. prmd provides tools for bootstrapping a description like this, verifying it's completeness and generating documentation from the specification.
9
+ [JSON Schema](http://json-schema.org/) provides a great way to describe
10
+ an API. prmd provides tools for bootstrapping a description like this,
11
+ verifying its completeness, and generating documentation from the
12
+ specification.
8
13
 
9
- The expectations for json-schema usage that are expected by prmd are described in [/docs/schemata.md](/docs/schemata.md).
14
+ The JSON Schema usage conventions expected by prmd are described in
15
+ [/docs/schemata.md](/docs/schemata.md).
10
16
 
11
- To learn more about json-schema in general, start with [this excellent guide](http://spacetelescope.github.io/understanding-json-schema/) and supplement with the [specification](http://json-schema.org/documentation.html).
17
+ To learn more about JSON Schema in general, start with
18
+ [this excellent guide](http://spacetelescope.github.io/understanding-json-schema/)
19
+ and supplement with the [specification](http://json-schema.org/documentation.html).
12
20
 
13
21
  ## Installation
14
22
 
15
23
  Add this line to your application's Gemfile:
16
24
 
17
- gem 'prmd'
25
+ ```ruby
26
+ gem 'prmd'
27
+ ```
18
28
 
19
29
  And then execute:
20
30
 
21
- $ bundle
31
+ ```console
32
+ $ bundle
33
+ ```
22
34
 
23
35
  Or install it yourself as:
24
36
 
25
- $ gem install prmd
37
+ ```console
38
+ $ gem install prmd
39
+ ```
26
40
 
27
41
  ## Usage
28
42
 
29
- Combine takes the path to a directory of schemas and combines them onto stdout. If -m or --meta is supplied, it will override defaults/metadata.
43
+ Combine takes the path to a schema file or directory of schema files and
44
+ combines them on to stdout. If -m or --meta is supplied, it will override
45
+ defaults/metadata:
30
46
 
31
- ```
32
- prmd combine <directory>
47
+ ```console
48
+ $ prmd combine <file or directory>
33
49
  ```
34
50
 
35
- Doc takes the path to a directory of schemas and outputs their documentation onto stdout. If -m or --meta is supplied, it will override defaults/metadata.
51
+ Doc takes the path to a combined schema and outputs documentation to stdout.
52
+ If -m or --meta is supplied, it will override defaults/metadata:
36
53
 
37
- ```
38
- prmd doc <directory_or_schema>
54
+ ```console
55
+ $ prmd doc <combined schema>
39
56
  ```
40
57
 
41
- Prepend file to the documentation output.
58
+ You can also prepend files to the documention with -p or --prepend:
42
59
 
43
- ```
44
- prmd doc -p header.md,overview.md <directory or schema>
60
+ ```console
61
+ $ prmd doc -p header.md,overview.md <directory or schema>
45
62
  ```
46
63
 
47
- Init optionally takes a resource as it's first argument and generates a new schema file to stdout (generically or using the resource name provided). If -m or --meta is supplied, it will override defaults/metadata.
64
+ Init optionally takes a resource as it's first argument and generates a
65
+ new schema file to stdout (generically or using the resource name
66
+ provided). If -m or --meta is supplied, it will override
67
+ defaults/metadata:
48
68
 
49
- ```
50
- prmd init
51
- prmd init <resource_name>
69
+ ```console
70
+ $ prmd init
71
+ $ prmd init <resource name>
52
72
  ```
53
73
 
54
- Verify takes a path to a directory of schemas or a particular schema file and checks to see if it matches expectations.
74
+ Verify takes a path to a combined schema and warns about missing attributes.
55
75
 
56
- ```
57
- prmd verify <directory_or_schema>
76
+ ```console
77
+ $ prmd verify <combined schema>
58
78
  ```
59
79
 
60
- Combining commands works too.
80
+ You can also chain commands as needed:
61
81
 
62
- ```
63
- prmd combine <directory> | prmd verify | prmd doc > schema.md
82
+ ```console
83
+ $ prmd combine <directory> | prmd verify | prmd doc > schema.md
64
84
  ```
65
85
 
66
86
  ## Contributing
data/bin/prmd CHANGED
@@ -6,29 +6,25 @@ options = {}
6
6
 
7
7
  commands = {
8
8
  combine: OptionParser.new do |opts|
9
- opts.banner = "prmd combine [options] <directory>"
9
+ opts.banner = "prmd combine [options] <file or directory>"
10
10
  opts.on("-m", "--meta meta.json", "Set defaults for schemata") do |m|
11
11
  options[:meta] = m
12
12
  end
13
13
  end,
14
14
  doc: OptionParser.new do |opts|
15
- opts.banner = "prmd doc [options] <schema>"
16
- opts.on("-m", "--meta meta.json", "Set defaults for schemata") do |m|
17
- options[:meta] = m
18
- end
15
+ opts.banner = "prmd doc [options] <combined schema>"
19
16
  opts.on("-p", "--prepend header,overview", Array, "Prepend files to output") do |p|
20
17
  options[:prepend] = p
21
18
  end
22
- opts.banner = "prmd doc [options] <directory>"
23
19
  end,
24
20
  init: OptionParser.new do |opts|
25
- opts.banner = "prmd init [options] <directory> <resource>"
21
+ opts.banner = "prmd init [options] <resource name>"
26
22
  opts.on("-m", "--meta meta.json", "Set defaults for schemata") do |m|
27
23
  options[:meta] = m
28
24
  end
29
25
  end,
30
26
  verify: OptionParser.new do |opts|
31
- opts.banner = "prmd verify [options] <directory or schema>"
27
+ opts.banner = "prmd verify [options] <combined schema>"
32
28
  end
33
29
  }
34
30
 
@@ -68,37 +64,33 @@ option.order!
68
64
 
69
65
  case command
70
66
  when :combine
71
- puts Prmd.combine(ARGV[0], options)
67
+ puts Prmd.combine(ARGV[0], options).to_s
72
68
  when :doc
73
- if ARGV.empty?
69
+ unless $stdin.tty?
74
70
  data = JSON.parse($stdin.read)
75
- schema = Prmd::Schema.new(data, options)
71
+ schema = Prmd::Schema.new(data)
76
72
  puts Prmd.doc(schema, options)
77
73
  else
78
- schema = Prmd::Schema.load(ARGV[0])
74
+ data = JSON.parse(File.read(ARGV[0]))
75
+ schema = Prmd::Schema.new(data)
79
76
  puts Prmd.doc(schema, options)
80
77
  end
81
78
  when :init
82
79
  puts Prmd.init(ARGV[0], options)
83
80
  when :verify
84
- errors = []
85
- if ARGV.empty?
81
+ data, errors = '', []
82
+ unless $stdin.tty?
86
83
  data = $stdin.read
87
- errors = Prmd.verify(JSON.parse(data))
88
- puts data
89
- elsif File.directory?(ARGV[0])
90
- Dir.glob(File.join(ARGV[0], '**/*.json')).each do |path|
91
- Prmd.verify(JSON.parse(File.read(path))).each do |error|
92
- errors << "#{path}: #{error}"
93
- end
94
- end
84
+ errors.concat(Prmd.verify(JSON.parse(data)))
95
85
  else
96
- Prmd.verify(JSON.parse(File.read(ARGV[0]))).each do |error|
86
+ data = JSON.parse(File.read(ARGV[0]))
87
+ Prmd.verify(data).each do |error|
97
88
  errors << "#{ARGV[0]}: #{error}"
98
89
  end
99
90
  end
100
91
  errors.each do |error|
101
- $stderr.puts error
92
+ $stderr.puts(error)
102
93
  end
103
94
  exit(1) unless errors.empty?
95
+ puts(data) unless $stdout.tty?
104
96
  end
data/docs/schemata.md CHANGED
@@ -42,7 +42,6 @@ Each attribute MUST include the following properties:
42
42
  Each attribute MAY include the following properties:
43
43
 
44
44
  * `pattern` - a regex encoded in a string that the valid values MUST match
45
- * `readOnly` - boolean value defining if the attribute can be modified, assumes `false` if omitted
46
45
  * `format` - format of the value. MUST be one of spec defined `["date-time", "email", "hostname", "ipv4", "ipv6", "uri"]` or defined by us `["uuid"]`
47
46
 
48
47
  Examples:
@@ -54,7 +53,6 @@ Examples:
54
53
  "description": "unique identifier of resource",
55
54
  "example": "01234567-89ab-cdef-0123-456789abcdef",
56
55
  "format": "uuid",
57
- "readOnly": true,
58
56
  "type": ["string"]
59
57
  },
60
58
  "url": {
@@ -1,5 +1,59 @@
1
1
  module Prmd
2
- def self.combine(directory, options={})
3
- Prmd::Schema.load(directory, options).to_s
2
+ def self.combine(path, options={})
3
+ files = if File.directory?(path)
4
+ Dir.glob(File.join(path, '**', '*.json')) - [options[:meta]]
5
+ else
6
+ [path]
7
+ end
8
+ schemas = files.map { |file| JSON.parse(File.read(file)) }
9
+
10
+ data = {
11
+ '$schema' => 'http://json-schema.org/draft-04/hyper-schema',
12
+ 'definitions' => {},
13
+ 'properties' => {},
14
+ 'type' => ['object']
15
+ }
16
+
17
+ if options[:meta] && File.exists?(options[:meta])
18
+ data.merge!(JSON.parse(File.read(options[:meta])))
19
+ end
20
+
21
+ schemas.each do |schema_data|
22
+ id = if schema_data['id']
23
+ schema_data['id'].split('/').last
24
+ end
25
+ next if id.nil? || id[0..0] == '_' # FIXME: remove this exception?
26
+
27
+ data['definitions'][id] = schema_data
28
+ reference_localizer = lambda do |datum|
29
+ case datum
30
+ when Array
31
+ datum.map {|element| reference_localizer.call(element)}
32
+ when Hash
33
+ if datum.has_key?('$ref')
34
+ if datum['$ref'].include?('/schema/')
35
+ $stderr.puts("`#{schema_data['id']}` `/schema/` prefixed refs are deprecated, use `/schemata/` prefixes")
36
+ datum['$ref'] = datum['$ref'].gsub(%r{/schema/([^#]*)#}, '#/definitions/\1')
37
+ end
38
+ datum['$ref'] = datum['$ref'].gsub(%r{/schemata/([^#]*)#}, '#/definitions/\1')
39
+ end
40
+ if datum.has_key?('href')
41
+ if datum['href'].include?('%2Fschema%2F')
42
+ $stderr.puts("`#{id}` `%2Fschema%2F` prefixed hrefs are deprecated, use `%2Fschemata%2F` prefixes")
43
+ datum['href'] = datum['href'].gsub(%r{%2Fschema%2F([^%]*)%23%2F}, '%23%2Fdefinitions%2F\1%2F')
44
+ end
45
+ datum['href'] = datum['href'].gsub(%r{%2Fschemata%2F([^%]*)%23%2F}, '%23%2Fdefinitions%2F\1%2F')
46
+ end
47
+ datum.each { |k,v| datum[k] = reference_localizer.call(v) }
48
+ else
49
+ datum
50
+ end
51
+ end
52
+ reference_localizer.call(data['definitions'][id])
53
+
54
+ data['properties'][id] = { '$ref' => "#/definitions/#{id}" }
55
+ end
56
+
57
+ Prmd::Schema.new(data)
4
58
  end
5
59
  end
@@ -2,7 +2,7 @@ def extract_attributes(schema, properties)
2
2
  attributes = []
3
3
  properties.each do |key, value|
4
4
  # found a reference to another element:
5
- value = schema.dereference(value)
5
+ _, value = schema.dereference(value)
6
6
  if value.has_key?('anyOf')
7
7
  descriptions = []
8
8
  examples = []
@@ -13,7 +13,7 @@ def extract_attributes(schema, properties)
13
13
  end
14
14
 
15
15
  anyof.each do |ref|
16
- nested_field = schema.dereference(ref)
16
+ _, nested_field = schema.dereference(ref)
17
17
  descriptions << nested_field['description']
18
18
  examples << nested_field['example']
19
19
  end
@@ -23,7 +23,10 @@ def extract_attributes(schema, properties)
23
23
  descriptions.first.gsub!(/ of (this )?.*/, "")
24
24
  descriptions[1..-1].map { |d| d.gsub!(/unique /, "") }
25
25
  end
26
- description = descriptions.join(" or ")
26
+
27
+ last = descriptions.pop
28
+ description = [descriptions.join(", "), last].join(" or ")
29
+
27
30
  example = doc_example(*examples)
28
31
  attributes << [key, "string", description, example]
29
32
 
@@ -59,7 +62,7 @@ end
59
62
 
60
63
  module Prmd
61
64
  def self.doc(schema, options={})
62
- root_url = schema['links'].find{|l| l['rel'] == 'root'}['href'] rescue schema['url']
65
+ root_url = schema['links'].find{|l| l['rel'] == 'self'}['href']
63
66
 
64
67
  doc = (options[:prepend] || []).map do |path|
65
68
  File.open(path, 'r').read + "\n"
@@ -73,20 +76,21 @@ module Prmd
73
76
  identifiers = if definition['definitions']['identity'].has_key?('anyOf')
74
77
  definition['definitions']['identity']['anyOf']
75
78
  else
76
- [definitions['definitions']['identity']]
79
+ [definition['definitions']['identity']]
77
80
  end
78
81
 
79
82
  identifiers = identifiers.map {|ref| ref['$ref'].split('/').last }
80
83
  end
81
84
  if definition['properties']
82
85
  definition['properties'].each do |key, value|
83
- unless value.has_key?('properties')
84
- serialization[key] = schema.dereference(value)['example']
85
- else
86
+ _, value = schema.dereference(value)
87
+ if value.has_key?('properties')
86
88
  serialization[key] = {}
87
89
  value['properties'].each do |k,v|
88
- serialization[key][k] = schema.dereference(v)['example']
90
+ serialization[key][k] = schema.dereference(v).last['example']
89
91
  end
92
+ else
93
+ serialization[key] = value['example']
90
94
  end
91
95
  end
92
96
  else
@@ -39,25 +39,20 @@ if original['definitions']
39
39
  when 'uuid'
40
40
  {
41
41
  'example' => '01234567-89ab-cdef-0123-456789abcdef',
42
- 'readOnly' => true,
43
42
  'type' => ['string']
44
43
  }
45
44
  when 'email'
46
45
  {
47
46
  'example' => 'username@example.com',
48
- 'readOnly' => false,
49
47
  'type' => ['string']
50
48
  }
51
49
  when 'date-time'
52
50
  {
53
51
  'example' => '2012-01-01T12:00:00Z',
54
- 'readOnly' => true,
55
52
  'type' => ['string']
56
53
  }
57
54
  else
58
- {
59
- 'readOnly' => false
60
- }
55
+ {}
61
56
  end
62
57
  expanded['definitions'][key] = default.merge!(value)
63
58
  end
@@ -3,10 +3,11 @@ module Prmd
3
3
  data = {
4
4
  '$schema' => 'http://json-schema.org/draft-04/hyper-schema',
5
5
  'title' => 'FIXME',
6
- 'type' => ['object'],
7
6
  'definitions' => {},
7
+ 'description' => 'FIXME',
8
8
  'links' => [],
9
- 'properties' => {}
9
+ 'properties' => {},
10
+ 'type' => ['object']
10
11
  }
11
12
 
12
13
  if options[:meta] && File.exists?(options[:meta])
@@ -16,83 +17,92 @@ module Prmd
16
17
  schema = Prmd::Schema.new(data)
17
18
 
18
19
  if resource
19
- schema['id'] = "schema/#{resource}"
20
+ if resource.include?('/')
21
+ parent, resource = resource.split('/')
22
+ end
23
+ schema['id'] = "schemata/#{resource}"
20
24
  schema['title'] = "#{schema['title']} - #{resource[0...1].upcase}#{resource[1..-1]}"
21
25
  schema['definitions'] = {
22
26
  "created_at" => {
23
27
  "description" => "when #{resource} was created",
24
28
  "example" => "2012-01-01T12:00:00Z",
25
29
  "format" => "date-time",
26
- "readOnly" => true,
27
30
  "type" => ["string"]
28
31
  },
29
32
  "id" => {
30
33
  "description" => "unique identifier of #{resource}",
31
34
  "example" => "01234567-89ab-cdef-0123-456789abcdef",
32
35
  "format" => "uuid",
33
- "readOnly" => true,
34
36
  "type" => ["string"]
35
37
  },
36
38
  "identity" => {
37
- "$ref" => "/schema/#{resource}#/definitions/id"
39
+ "$ref" => "/schemata/#{resource}#/definitions/id"
38
40
  },
39
41
  "updated_at" => {
40
42
  "description" => "when #{resource} was updated",
41
43
  "example" => "2012-01-01T12:00:00Z",
42
44
  "format" => "date-time",
43
- "readOnly" => true,
44
45
  "type" => ["string"]
45
46
  }
46
47
  }
47
48
  schema['links'] = [
48
49
  {
49
- "description" => "Create a new #{resource}.",
50
- "href" => "/#{resource}s",
51
- "method" => "POST",
52
- "rel" => "create",
53
- "schema" => {
50
+ "description" => "Create a new #{resource}.",
51
+ "href" => "/#{resource}s",
52
+ "method" => "POST",
53
+ "rel" => "create",
54
+ "schema" => {
54
55
  "properties" => {},
55
56
  "type" => ["object"]
56
57
  },
57
- "title" => "Create"
58
+ "title" => "Create"
58
59
  },
59
60
  {
60
- "description" => "Delete an existing #{resource}.",
61
- "href" => "/#{resource}s/{(%2Fschema%2F#{resource}%23%2Fdefinitions%2Fidentity)}",
62
- "method" => "DELETE",
63
- "rel" => "destroy",
64
- "title" => "Delete"
61
+ "description" => "Delete an existing #{resource}.",
62
+ "href" => "/#{resource}s/{(%2Fschemata%2F#{resource}%23%2Fdefinitions%2Fidentity)}",
63
+ "method" => "DELETE",
64
+ "rel" => "destroy",
65
+ "title" => "Delete"
65
66
  },
66
67
  {
67
- "description" => "Info for existing #{resource}.",
68
- "href" => "/#{resource}s/{(%2Fschema%2F#{resource}%23%2Fdefinitions%2Fidentity)}",
69
- "method" => "GET",
70
- "rel" => "self",
71
- "title" => "Info"
68
+ "description" => "Info for existing #{resource}.",
69
+ "href" => "/#{resource}s/{(%2Fschemata%2F#{resource}%23%2Fdefinitions%2Fidentity)}",
70
+ "method" => "GET",
71
+ "rel" => "self",
72
+ "title" => "Info"
72
73
  },
73
74
  {
74
- "description" => "List existing #{resource}.",
75
- "href" => "/#{resource}s",
76
- "method" => "GET",
77
- "rel" => "instances",
78
- "title" => "List"
75
+ "description" => "List existing #{resource}s.",
76
+ "href" => "/#{resource}s",
77
+ "method" => "GET",
78
+ "rel" => "instances",
79
+ "title" => "List"
79
80
  },
80
81
  {
81
- "description" => "Update an existing #{resource}.",
82
- "href" => "/#{resource}s/{(%2Fschema%2F#{resource}%23%2Fdefinitions%2Fidentity)}",
83
- "method" => "PATCH",
84
- "rel" => "update",
85
- "schema" => {
82
+ "description" => "Update an existing #{resource}.",
83
+ "href" => "/#{resource}s/{(%2Fschemata%2F#{resource}%23%2Fdefinitions%2Fidentity)}",
84
+ "method" => "PATCH",
85
+ "rel" => "update",
86
+ "schema" => {
86
87
  "properties" => {},
87
88
  "type" => ["object"]
88
89
  },
89
- "title" => "Update"
90
+ "title" => "Update"
90
91
  }
91
92
  ]
93
+ if parent
94
+ schema['links'] << {
95
+ "description" => "List existing #{resource}s for existing #{parent}.",
96
+ "href" => "/#{parent}s/{(%2Fschemata%2F#{parent}%23%2Fdefinitions%2Fidentity)}/#{resource}s",
97
+ "method" => "GET",
98
+ "rel" => "instances",
99
+ "title" => "List"
100
+ }
101
+ end
92
102
  schema['properties'] = {
93
- "created_at" => { "$ref" => "/schema/#{resource}#/definitions/created_at" },
94
- "id" => { "$ref" => "/schema/#{resource}#/definitions/id" },
95
- "updated_at" => { "$ref" => "/schema/#{resource}#/definitions/updated_at" }
103
+ "created_at" => { "$ref" => "/schemata/#{resource}#/definitions/created_at" },
104
+ "id" => { "$ref" => "/schemata/#{resource}#/definitions/id" },
105
+ "updated_at" => { "$ref" => "/schemata/#{resource}#/definitions/updated_at" }
96
106
  }
97
107
  end
98
108
 
@@ -1,11 +1,23 @@
1
1
  module Prmd
2
2
  def self.verify(schema)
3
3
  errors = []
4
+ errors << verify_schema(schema)
5
+ if schema['definitions']
6
+ schema['definitions'].each do |key, value|
7
+ errors << verify_schema(value)
8
+ errors << verify_definitions_and_links(value)
9
+ end
10
+ end
11
+ errors.flatten!
12
+ end
13
+
14
+ def self.verify_schema(schema)
15
+ errors = []
4
16
 
5
17
  id = schema['id']
6
18
 
7
19
  missing_requirements = []
8
- %w{description id $schema title type definitions links properties}.each do |requirement|
20
+ %w{$schema definitions description id links properties title type}.each do |requirement|
9
21
  unless schema.has_key?(requirement)
10
22
  missing_requirements << requirement
11
23
  end
@@ -14,6 +26,14 @@ module Prmd
14
26
  errors << "Missing `#{id}#/#{missing_requirement}`"
15
27
  end
16
28
 
29
+ errors
30
+ end
31
+
32
+ def self.verify_definitions_and_links(schema)
33
+ errors = []
34
+
35
+ id = schema['id']
36
+
17
37
  if schema['definitions']
18
38
  unless schema['definitions'].has_key?('identity')
19
39
  errors << "Missing `#{id}#/definitions/identity`"
@@ -21,7 +41,7 @@ module Prmd
21
41
  schema['definitions'].each do |key, value|
22
42
  missing_requirements = []
23
43
  unless key == 'identity'
24
- %w{description readOnly type}.each do |requirement|
44
+ %w{description type}.each do |requirement|
25
45
  unless schema['definitions'][key].has_key?(requirement)
26
46
  missing_requirements << requirement
27
47
  end
@@ -59,6 +79,8 @@ module Prmd
59
79
  errors << "Missing #{missing_requirement} in `#{link}` link for `#{id}`"
60
80
  end
61
81
  end
82
+ else
83
+ errors << "Missing `#{id}/links`"
62
84
  end
63
85
 
64
86
  errors
data/lib/prmd/schema.rb CHANGED
@@ -9,61 +9,13 @@ module Prmd
9
9
  @data[key] = value
10
10
  end
11
11
 
12
- def self.load(path, options={})
13
- unless File.directory?(path)
14
- data = JSON.parse(File.read(path))
15
- else
16
- data = {
17
- '$schema' => 'http://json-schema.org/draft-04/hyper-schema',
18
- 'definitions' => {},
19
- 'properties' => {},
20
- 'type' => ['object']
21
- }
22
-
23
- if options[:meta] && File.exists?(options[:meta])
24
- data.merge!(JSON.parse(File.read(options[:meta])))
25
- end
26
-
27
- Dir.glob(File.join(path, '**', '*.json')).each do |schema|
28
- schema_data = JSON.parse(File.read(schema))
29
- id = if schema_data['id']
30
- schema_data['id'].gsub('schema/', '')
31
- end
32
- next if id.nil? || id[0..0] == '_' # FIXME: remove this exception?
33
-
34
- data['definitions'][id] = schema_data
35
- reference_localizer = lambda do |datum|
36
- case datum
37
- when Array
38
- datum.map {|element| reference_localizer.call(element)}
39
- when Hash
40
- if datum.has_key?('$ref')
41
- datum['$ref'] = datum['$ref'].gsub(%r{/schema/([^#]*)#}, '#/definitions/\1')
42
- end
43
- if datum.has_key?('href')
44
- datum['href'] = datum['href'].gsub(%r{%2Fschema%2F([^%]*)%23%2F}, '%23%2Fdefinitions%2F\1%2F')
45
- end
46
- datum.each { |k,v| datum[k] = reference_localizer.call(v) }
47
- else
48
- datum
49
- end
50
- end
51
- reference_localizer.call(data['definitions'][id])
52
-
53
- data['properties'][id] = { '$ref' => "#/definitions/#{id}" }
54
- end
55
- end
56
-
57
- self.new(data)
58
- end
59
-
60
12
  def initialize(new_data = {})
61
13
  convert_type_to_array = lambda do |datum|
62
14
  case datum
63
15
  when Array
64
16
  datum.map { |element| convert_type_to_array.call(element) }
65
17
  when Hash
66
- if datum.has_key?('type')
18
+ if datum.has_key?('type') && datum['type'].is_a?(String)
67
19
  datum['type'] = [*datum['type']]
68
20
  end
69
21
  datum.each { |k,v| datum[k] = convert_type_to_array.call(v) }
@@ -77,19 +29,26 @@ module Prmd
77
29
  def dereference(reference)
78
30
  if reference.is_a?(Hash)
79
31
  if reference.has_key?('$ref')
80
- key = reference['$ref']
32
+ value = reference.dup
33
+ key = value.delete('$ref')
81
34
  else
82
- return reference # no dereference needed
35
+ return [nil, reference] # no dereference needed
83
36
  end
84
37
  else
85
- key = reference
38
+ key, value = reference, {}
86
39
  end
87
40
  begin
88
41
  datum = @data
89
42
  key.gsub(%r{[^#]*#/}, '').split('/').each do |fragment|
90
43
  datum = datum[fragment]
91
44
  end
92
- datum
45
+ # last dereference will have nil key, so compact it out
46
+ # [-2..-1] should be the final key reached before deref
47
+ dereferenced_key, dereferenced_value = dereference(datum)
48
+ [
49
+ [key, dereferenced_key].compact.last,
50
+ [dereferenced_value, value].inject({}) { |composite, element| composite.merge(element) }
51
+ ]
93
52
  rescue => error
94
53
  $stderr.puts("Failed to dereference `#{key}`")
95
54
  raise(error)
data/lib/prmd/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Prmd
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -24,8 +24,13 @@
24
24
  <%- definition['links'].each do |link, datum| %>
25
25
  <%- path = link['href'].gsub(%r|(\{\([^\)]+\)\})|) do |ref|
26
26
  ref = ref.gsub('%2F', '/').gsub('%23', '#').gsub(%r|[\{\(\)\}]|, '')
27
- resource = ref.split('#/definitions/').last.split('/definitions/identity').first
28
- '{' + resource + '_' + schema.dereference(ref)['anyOf'].map {|r| r['$ref'].split('/').last}.join('_or_') + '}'
27
+ resource = ref.match(%r{^#/definitions/([^/]*)}).captures.first
28
+ identity_key, identity_value = schema.dereference(ref)
29
+ if identity_value.has_key?('anyOf')
30
+ '{' + resource + '_' + identity_value['anyOf'].map {|r| r['$ref'].split('/').last}.join('_or_') + '}'
31
+ else
32
+ '{' + resource + '_' + identity_key.split('/').last + '}'
33
+ end
29
34
  end -%>
30
35
  ### <%= title %> <%= link['title'] %>
31
36
  <%= link['description'] %>
@@ -53,31 +58,52 @@ end -%>
53
58
 
54
59
  #### Curl Example
55
60
  ```term
56
- $ curl -n -X <%= link['method'] %> <%= root_url %><%= path.gsub(/{([^}]*)}/) {|match| '$' + match.gsub(/[{}]/, '').gsub('-', '_').upcase} %><%- if link.has_key?('schema') && (link['schema'].has_key?('properties') || link['schema'].has_key?('example')) %> \
57
- -H "Content-Type: application/json" \
58
- <%-
59
- data = {}
60
- if link['schema']['properties']
61
- link['schema']['properties'].each do |key, value|
62
- if value.has_key?('anyOf')
63
- id_ref = value['anyOf'].detect {|ref| ref['$ref'].split('/').last == 'id'}
64
- data[key] = schema.dereference(id_ref)['example']
65
- elsif value.has_key?('properties')
66
- data[key] = {}
67
- value['properties'].each do |k,v|
68
- data[key][k] = v['example']
61
+ <%- path = path.gsub(/{([^}]*)}/) {|match| '$' + match.gsub(/[{}]/, '')} %>
62
+ <%- if link.has_key?('schema') && (link['schema'].has_key?('properties') || link['schema'].has_key?('example')) %>
63
+ <%-
64
+ data = {}
65
+ if link['schema']['properties']
66
+ link['schema']['properties'].each do |key, value|
67
+ if value.has_key?('anyOf')
68
+ id_ref = value['anyOf'].detect {|ref| ref['$ref'].split('/').last == 'id'}
69
+ data[key] = schema.dereference(id_ref).last['example']
70
+ elsif value.has_key?('properties')
71
+ data[key] = {}
72
+ value['properties'].each do |k,v|
73
+ data[key][k] = schema.dereference(v).last['example']
74
+ end
75
+ else
76
+ data[key] = schema.dereference(value).last['example']
69
77
  end
70
- else
71
- data[key] = value['example']
72
78
  end
79
+ else
80
+ data.merge!(link['schema']['example'])
73
81
  end
74
- else
75
- data.merge!(link['schema']['example'])
76
- end
77
- %>
82
+ %>
83
+ <%- if link['method'].upcase == 'GET' %>
84
+ <%-
85
+ unless data.empty?
86
+ path << '?'
87
+ data.sort_by {|k,_| k.to_s }.each do |key, values|
88
+ if values.nil?
89
+ path << key.to_s << '&'
90
+ else
91
+ [values].flatten.each do |value|
92
+ path << key.to_s << '=' << CGI.escape(value.to_s) << '&'
93
+ end
94
+ end
95
+ end
96
+ path.chop! # remove trailing '&'
97
+ end
98
+ %>
99
+ $ curl -n -X <%= link['method'] %> <%= root_url %><%= path %>
100
+ <%- else %>
101
+ $ curl -n -X <%= link['method'] %> <%= root_url %><%= path %> \
102
+ -H "Content-Type: application/json" \
78
103
  -d '<%= data.to_json %>'
104
+ <%- end %>
79
105
  <%- else %>
80
-
106
+ $ curl -n -X <%= link['method'] %> <%= root_url %><%= path %>
81
107
  <%- end %>
82
108
  ```
83
109
 
@@ -89,12 +115,6 @@ when 'create'
89
115
  else
90
116
  '200 OK'
91
117
  end %>
92
- <%- if link['rel'] == 'instances' && identifiers %>
93
- Accept-Range: <%= identifiers.join(', ') %>
94
- Content-Range: id 01234567-89ab-cdef-0123-456789abcdef..01234567-89ab-cdef-0123-456789abcdef; max=200
95
- <%- end %>
96
- ETag: "0123456789abcdef0123456789abcdef"
97
- RateLimit-Remaining: 1200
98
118
  ```
99
119
  ```javascript```
100
120
  <%- if link['rel'] == 'instances' %>
data/lib/prmd.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "diff-lcs"
1
+ require "cgi"
2
2
  require "erubis"
3
3
  require "json"
4
4
 
data/prmd.gemspec CHANGED
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Prmd::VERSION
9
9
  spec.authors = ["geemus"]
10
10
  spec.email = ["geemus@gmail.com"]
11
- spec.description = %q{schema to rule them all}
12
- spec.summary = %q{schema to rule them all}
13
- spec.homepage = ""
11
+ spec.description = %q{scaffold, verify and generate docs from JSON Schema}
12
+ spec.summary = %q{JSON Schema tooling}
13
+ spec.homepage = "https://github.com/heroku/prmd"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
@@ -18,10 +18,9 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "diff-lcs"
22
- spec.add_dependency "erubis"
21
+ spec.add_dependency "erubis", "~> 2.7"
23
22
 
24
- spec.add_development_dependency "bundler", "~> 1.3"
25
- spec.add_development_dependency "rake"
26
- spec.add_development_dependency "minitest"
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake", "~> 10.2"
25
+ spec.add_development_dependency "minitest", "~> 5.3"
27
26
  end
data/test/helpers.rb CHANGED
@@ -2,9 +2,9 @@ require "minitest/autorun"
2
2
  require "prmd"
3
3
 
4
4
  def input_schemas_path
5
- @data_path ||= File.join(File.dirname(__FILE__), 'schemas', 'input')
5
+ @@data_path ||= File.join(File.dirname(__FILE__), 'schemas', 'input')
6
6
  end
7
7
 
8
8
  def user_input_schema
9
- @user_input_schema ||= Prmd::Schema.load(File.join(input_schemas_path, 'user.json'))
9
+ @@user_input_schema ||= Prmd.combine(File.join(input_schemas_path, 'user.json'))
10
10
  end
data/test/schema_test.rb CHANGED
@@ -1,13 +1,34 @@
1
- require File.join(File.dirname(__FILE__), 'helpers')
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helpers'))
2
2
 
3
3
  class SchemaTest < Minitest::Unit::TestCase
4
4
  def test_dereference_with_ref
5
- ref = user_input_schema.dereference({ "$ref" => "/schema/user#/definitions/id" })
6
- assert_equal ref, user_input_schema["definitions"]["id"]
5
+ key, value = user_input_schema.dereference({
6
+ '$ref' => '#/definitions/user/definitions/id'
7
+ })
8
+ assert_equal(key, '#/definitions/user/definitions/id')
9
+ assert_equal(value, user_input_schema['definitions']['user']['definitions']['id'])
7
10
  end
8
11
 
9
12
  def test_dereference_without_ref
10
- ref = user_input_schema.dereference("/schema/user#/definitions/id")
11
- assert_equal ref, user_input_schema["definitions"]["id"]
13
+ key, value = user_input_schema.dereference('#/definitions/user/definitions/id')
14
+ assert_equal(key, '#/definitions/user/definitions/id')
15
+ assert_equal(value, user_input_schema['definitions']['user']['definitions']['id'])
16
+ end
17
+
18
+ def test_dereference_with_nested_ref
19
+ key, value = user_input_schema.dereference({
20
+ '$ref' => '#/definitions/user/definitions/identity'
21
+ })
22
+ assert_equal(key, '#/definitions/user/definitions/id')
23
+ assert_equal(value, user_input_schema['definitions']['user']['definitions']['id'])
24
+ end
25
+
26
+ def test_dereference_with_local_context
27
+ key, value = user_input_schema.dereference({
28
+ '$ref' => '#/definitions/user/properties/id',
29
+ 'override' => true
30
+ })
31
+ assert_equal(key, '#/definitions/user/definitions/id')
32
+ assert_equal(value, {'override' => true}.merge(user_input_schema['definitions']['user']['definitions']['id']))
12
33
  end
13
34
  end
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/hyper-schema",
3
+ "definitions": {},
4
+ "description": "API lets you interact with service",
5
+ "links": [{
6
+ "href": "https://api.example.com",
7
+ "rel": "self"
8
+ }],
9
+ "properties": {},
10
+ "title": "API",
11
+ "type": [
12
+ "object"
13
+ ]
14
+ }
@@ -9,7 +9,6 @@
9
9
  "description": "when user was created",
10
10
  "example": "2012-01-01T12:00:00Z",
11
11
  "format": "date-time",
12
- "readOnly": true,
13
12
  "type": [
14
13
  "string"
15
14
  ]
@@ -18,19 +17,17 @@
18
17
  "description": "unique identifier of user",
19
18
  "example": "01234567-89ab-cdef-0123-456789abcdef",
20
19
  "format": "uuid",
21
- "readOnly": true,
22
20
  "type": [
23
21
  "string"
24
22
  ]
25
23
  },
26
24
  "identity": {
27
- "$ref": "/schema/user#/definitions/id"
25
+ "$ref": "/schemata/user#/definitions/id"
28
26
  },
29
27
  "updated_at": {
30
28
  "description": "when user was updated",
31
29
  "example": "2012-01-01T12:00:00Z",
32
30
  "format": "date-time",
33
- "readOnly": true,
34
31
  "type": [
35
32
  "string"
36
33
  ]
@@ -53,14 +50,14 @@
53
50
  },
54
51
  {
55
52
  "description": "Delete an existing user.",
56
- "href": "/users/{(%2Fschema%2Fuser%23%2Fdefinitions%2Fidentity)}",
53
+ "href": "/users/{(%2Fschemata%2Fuser%23%2Fdefinitions%2Fidentity)}",
57
54
  "method": "DELETE",
58
55
  "rel": "destroy",
59
56
  "title": "Delete"
60
57
  },
61
58
  {
62
59
  "description": "Info for existing user.",
63
- "href": "/users/{(%2Fschema%2Fuser%23%2Fdefinitions%2Fidentity)}",
60
+ "href": "/users/{(%2Fschemata%2Fuser%23%2Fdefinitions%2Fidentity)}",
64
61
  "method": "GET",
65
62
  "rel": "self",
66
63
  "title": "Info"
@@ -74,7 +71,7 @@
74
71
  },
75
72
  {
76
73
  "description": "Update an existing user.",
77
- "href": "/users/{(%2Fschema%2Fuser%23%2Fdefinitions%2Fidentity)}",
74
+ "href": "/users/{(%2Fschemata%2Fuser%23%2Fdefinitions%2Fidentity)}",
78
75
  "method": "PATCH",
79
76
  "rel": "update",
80
77
  "schema": {
@@ -89,14 +86,14 @@
89
86
  ],
90
87
  "properties": {
91
88
  "created_at": {
92
- "$ref": "/schema/user#/definitions/created_at"
89
+ "$ref": "/schemata/user#/definitions/created_at"
93
90
  },
94
91
  "id": {
95
- "$ref": "/schema/user#/definitions/id"
92
+ "$ref": "/schemata/user#/definitions/id"
96
93
  },
97
94
  "updated_at": {
98
- "$ref": "/schema/user#/definitions/updated_at"
95
+ "$ref": "/schemata/user#/definitions/updated_at"
99
96
  }
100
97
  },
101
- "id": "schema/user"
98
+ "id": "schemata/user"
102
99
  }
metadata CHANGED
@@ -1,43 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prmd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - geemus
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-07 00:00:00.000000000 Z
11
+ date: 2014-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: diff-lcs
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
13
  - !ruby/object:Gem::Dependency
28
14
  name: erubis
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
- - - ! '>='
17
+ - - ~>
32
18
  - !ruby/object:Gem::Version
33
- version: '0'
19
+ version: '2.7'
34
20
  type: :runtime
35
21
  prerelease: false
36
22
  version_requirements: !ruby/object:Gem::Requirement
37
23
  requirements:
38
- - - ! '>='
24
+ - - ~>
39
25
  - !ruby/object:Gem::Version
40
- version: '0'
26
+ version: '2.7'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: bundler
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -56,31 +42,31 @@ dependencies:
56
42
  name: rake
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
- - - ! '>='
45
+ - - ~>
60
46
  - !ruby/object:Gem::Version
61
- version: '0'
47
+ version: '10.2'
62
48
  type: :development
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
- - - ! '>='
52
+ - - ~>
67
53
  - !ruby/object:Gem::Version
68
- version: '0'
54
+ version: '10.2'
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: minitest
71
57
  requirement: !ruby/object:Gem::Requirement
72
58
  requirements:
73
- - - ! '>='
59
+ - - ~>
74
60
  - !ruby/object:Gem::Version
75
- version: '0'
61
+ version: '5.3'
76
62
  type: :development
77
63
  prerelease: false
78
64
  version_requirements: !ruby/object:Gem::Requirement
79
65
  requirements:
80
- - - ! '>='
66
+ - - ~>
81
67
  - !ruby/object:Gem::Version
82
- version: '0'
83
- description: schema to rule them all
68
+ version: '5.3'
69
+ description: scaffold, verify and generate docs from JSON Schema
84
70
  email:
85
71
  - geemus@gmail.com
86
72
  executables:
@@ -111,8 +97,9 @@ files:
111
97
  - prmd.gemspec
112
98
  - test/helpers.rb
113
99
  - test/schema_test.rb
100
+ - test/schemas/input/meta.json
114
101
  - test/schemas/input/user.json
115
- homepage: ''
102
+ homepage: https://github.com/heroku/prmd
116
103
  licenses:
117
104
  - MIT
118
105
  metadata: {}
@@ -135,9 +122,10 @@ rubyforge_project:
135
122
  rubygems_version: 2.2.2
136
123
  signing_key:
137
124
  specification_version: 4
138
- summary: schema to rule them all
125
+ summary: JSON Schema tooling
139
126
  test_files:
140
127
  - test/helpers.rb
141
128
  - test/schema_test.rb
129
+ - test/schemas/input/meta.json
142
130
  - test/schemas/input/user.json
143
131
  has_rdoc: