prmd 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: