json-schema-generator2 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bbc6dd023b2df953628f8a813a30af6fe92c74c6da74ac4729c1c5c48a221f9c
4
+ data.tar.gz: ea00fbd7e22c89010dbab29d9dead34aeeb5fc21fe7dd46d6836448af430b42c
5
+ SHA512:
6
+ metadata.gz: 821ffb27d6d612d7b78e8b7932080af6c65081f4c01f87331cc554d12977e7fe0ed25dae6765470507316655ff9856d2e1afffaf355f4871e1cd9ca17b999d45
7
+ data.tar.gz: ef218c2f8e6463c7d8a7a3720692bcbcab77017a59c0ea5759e545575363bcd16beda2e515906ea7d007aec51e494c2ce3f06621a0b26baff0d5451ac0564ff4
@@ -0,0 +1,17 @@
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
@@ -0,0 +1,3 @@
1
+ [submodule "kata"]
2
+ path = kata
3
+ url = https://github.com/maxlinc/json-schema-kata.git
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - jruby-19mode
6
+ - rbx-2.2.1
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: rbx-2.2.1
10
+ before_install:
11
+ - git submodule update --init --recursive
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in json-schema-generator.gemspec
4
+ gemspec
5
+
6
+ platforms :rbx do
7
+ gem 'rubysl'
8
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Max Lincoln
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.
@@ -0,0 +1,78 @@
1
+ # JSON::SchemaGenerator
2
+
3
+ JSON::SchemaGenerator tries to save you some time writing [json-schemas](http://json-schema.org/) based on existing data. I know that there are multiple valid json-schemas that could be generated for any given sample, so I won't generate the exact schema you want. Our goal is just to make reasonable assumptions that get you close, so you can generate the basic structure and then customize your schema, rather than writing hundreds of lines by hand.
4
+
5
+ There are [many json-schema validators], but only a few generators. The best generator I've found is a closed-source web app so you can't embed it. I couldn't find anything to embed within an open-source Ruby project ([Pacto](https://github.com/thoughtworks/pacto)), so I hacked one together quickly.
6
+
7
+ My quick, yak-shaving implementaiton wasn't designed for maintainability, but I wrote a test suite that gives me the confidence to rewrite or even port to other languages. The tests are in [json-schema-kata](https://github.com/maxlinc/json-schema-kata). I called it a kata for a reason:
8
+
9
+ > Code Kata is an attempt to bring this element of practice to software development. A kata is an exercise in karate where you repeat a form many, many times, making little improvements in each. The intent behind code kata is similar.
10
+ >
11
+ > Dave Thomas [Code Kata](http://codekata.pragprog.com/2007/01/code_kata_backg.html)
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'json-schema-generator'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install json-schema-generator
26
+
27
+ ## Usage
28
+
29
+ Command line:
30
+ ```sh
31
+ # Usage: json-schema-generator --help
32
+ # Simple example:
33
+ $ json-schema-generator my_sample.json --schema-version draft3
34
+ ```
35
+
36
+ Ruby:
37
+ ```ruby
38
+ file = 'my_sample.json' # or any identifier for the description
39
+ JSON::SchemaGenerator.generate file, File.read(file), {:schema_version => 'draft3'}
40
+ ```
41
+
42
+ ## Features
43
+
44
+ JSON::SchemaGenerate has the following features or assumptions for generating "best-guess" schemas:
45
+
46
+ * **Schema Versions**: Support draft4 of json-schema.
47
+ * **Options**:
48
+ * **Defaults**: Can generate default values.
49
+ * **Descriptions**: Can generate a description indicating where the schema came from.
50
+ * **Features/Assumptions**:
51
+ * **Detecting optional properties:** if you are using arrays (even arrays with complex nested types), the generator will use all available data to figure detect if a field is optional.
52
+ * **Assume required:** in all other cases, I assume everything I find in the sample is required. I believe it is better to generate a schema that is too strict than too lenient. It is easy to review and fix false negatives, by updating the schema to mark those items as optional. A false positive will go unnoticed and will not point you towards a solution.
53
+ * **Detect types:** I detect objects, arrays, strings, integers, numbers and booleans.
54
+
55
+ ## Known issues
56
+
57
+ * Currently assumes the root element is a hash. Generation will fail for data that has an array as the root.
58
+
59
+ ## Tests
60
+
61
+ This was a [yak shaving](http://en.wiktionary.org/wiki/yak_shaving) project. I needed to generate schemas for a ruby project. I wanted to embed a schema generator within a larger open-source project ([Pacto](https://github.com/thoughtworks/pacto)), and couldn't find one I could use. So I wrote one.
62
+
63
+ There are [many json-schema validators], but only a few generators. The best generator I've found is a closed-source Web app so you can't embed it. I'm hoping more people will write generators in more languages. I also hope someone replaces my quick "yak-shaving" implementation
64
+ There are many json-schema validators available in many different programming languages. However, there are only a few generators. I hacked this together quickly because I wanted one I could easily use in Ruby. My implementaiton is yak-shaving, not a well-design and maintainable solution. So I wanted acceptance tests that are fully decoupled from the implementation - even the programming language - so I can completely re-write my solution or port it to other languages.
65
+
66
+ The tests are in the json-schema-kata project. The test runner is ruby, but it makes no assumptions about the implementation language. Feel free to use my tests to port to any language.
67
+
68
+ ## Contributing
69
+
70
+ Contributions Welcome! It's easy to contribute tests to the json-schema-kata project. You just need to add a sample and what you think the generated schema should look like for each supported json-schema version (draft3, draft4, etc).
71
+
72
+ Rewrites, ports, tutorials: I called the tests a "kata" because it is a small problem that is good for practicing programming skills, but could be solved many different ways. Can you implement a generator using functional programming? An internal DSL? JSONPath? Seven languages in seven Weeks? Try it out, hone your skills, and share your results.
73
+
74
+ 1. Fork it
75
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
76
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
77
+ 4. Push to the branch (`git push origin my-new-feature`)
78
+ 5. Create new Pull Request
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :default => :test
4
+
5
+ task :test do
6
+ ENV['PATH'] = ENV['PATH'] + File::PATH_SEPARATOR + File.expand_path('bin', Dir.pwd)
7
+ puts ENV['PATH']
8
+ Bundler.with_clean_env do
9
+ Dir.chdir 'kata' do
10
+ system 'bundle install'
11
+ system 'bundle exec rake'
12
+ end
13
+ end
14
+ fail 'Tests did not pass!' unless $?.success?
15
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json-schema-generator'
4
+ require 'json/schema_generator_cli'
5
+ JSON::SchemaGeneratorCLI.new(ARGV.dup).execute!
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'json/schema_generator/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "json-schema-generator2"
8
+ spec.version = JSON::SchemaGenerator::VERSION
9
+ spec.authors = ["Max Lincoln"]
10
+ spec.email = ["max@devopsy.com"]
11
+ spec.description = %q{A very basic json-schema generator}
12
+ spec.summary = %q{A very basic json-schema generator}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "jsonpath"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "json-schema"
27
+ spec.add_development_dependency "travis"
28
+ end
@@ -0,0 +1,5 @@
1
+ require 'digest/sha1'
2
+ require 'stringio'
3
+ require 'json'
4
+ require 'json/schema_generator'
5
+ require 'json/schema_generator_cli'
@@ -0,0 +1,130 @@
1
+ require 'json/schema_generator/statement_group'
2
+ require 'json/schema_generator/brute_force_required_search'
3
+
4
+ module JSON
5
+ class SchemaGenerator
6
+
7
+ class << self
8
+ def generate name, data, opts = {}
9
+ JSON::SchemaGenerator.new(name, opts).generate data
10
+ end
11
+ end
12
+
13
+ def initialize name, opts = {}
14
+ @defaults = opts[:defaults]
15
+ @allow_null = opts[:allow_null]
16
+
17
+ @buffer = StringIO.new
18
+ @name = name
19
+ end
20
+
21
+ def generate raw_data
22
+ data = JSON.load(raw_data)
23
+ @brute_search = BruteForceRequiredSearch.new data
24
+
25
+ statement_group = StatementGroup.new
26
+ statement_group.add "\"$schema\": \"http://json-schema.org/draft4/schema#\""
27
+ statement_group.add "\"description\": \"Generated from json_schema_generator #rusteze\""
28
+ case data
29
+ when Array
30
+ $stop = true
31
+ create_array(statement_group, data, detect_required(data))
32
+ else
33
+ create_hash(statement_group, data, detect_required(data))
34
+ end
35
+ @buffer.puts statement_group
36
+ result
37
+ end
38
+
39
+ protected
40
+
41
+ def create_primitive(statement_group, key, value, required_keys)
42
+ if required_keys.nil?
43
+ required = true
44
+ else
45
+ required = required_keys.include? key
46
+ end
47
+
48
+ type = case value
49
+ when TrueClass, FalseClass
50
+ "boolean"
51
+ when String
52
+ "string"
53
+ when Integer, Float
54
+ "number"
55
+ else
56
+ raise "Unknown Primitive Type for #{key}! #{value.class}"
57
+ end
58
+
59
+ if @allow_null
60
+ statement_group.add "\"type\": #{[type, "null"]}"
61
+ else
62
+ statement_group.add "\"type\": \"#{type}\""
63
+ end
64
+ # statement_group.add "\"oneOf\": [{\"type\": \"#{type}\"}, {\"type\": \"null\"}]"
65
+ statement_group.add "\"default\": #{value.inspect}" if @defaults
66
+ end
67
+
68
+ def create_values(key, value, required_keys = nil, in_array = false)
69
+ statement_group = StatementGroup.new key
70
+ case value
71
+ when NilClass
72
+ when TrueClass, FalseClass, String, Integer, Float
73
+ create_primitive(statement_group, key, value, required_keys)
74
+ when Array
75
+ create_array(statement_group, value, detect_required(value))
76
+ when Hash
77
+ if in_array
78
+ create_hash(statement_group, value, required_keys)
79
+ else
80
+ create_hash(statement_group, value, detect_required(value))
81
+ end
82
+ else
83
+ raise "Unknown Type for #{key}! #{value.class}"
84
+ end
85
+ statement_group
86
+ end
87
+
88
+ def create_hash(statement_group, data, required_keys)
89
+ statement_group.add '"type": "object"'
90
+ required_keys ||= []
91
+ required_string = required_keys.map(&:inspect).join ', '
92
+ statement_group.add "\"required\": [#{required_string}]" unless required_keys.empty?
93
+ statement_group.add create_hash_properties data, required_keys
94
+ statement_group
95
+ end
96
+
97
+ def create_hash_properties(data, required_keys)
98
+ statement_group = StatementGroup.new "properties"
99
+ data.collect do |k,v|
100
+ @brute_search.push k,v
101
+ statement_group.add create_values k, v, required_keys
102
+ @brute_search.pop
103
+ end
104
+ statement_group
105
+ end
106
+
107
+ def create_array(statement_group, data, required_keys)
108
+ statement_group.add '"type": "array"'
109
+
110
+ # FIXME - Code assumes that all items in the array have the same structure
111
+ # Assume lowest common denominator - allow 0 items and unique not required
112
+ statement_group.add '"minItems": 0'
113
+
114
+ # TODO - consider a eq? method for StatementGroup class to evaluate LCD schema from all items in array
115
+ statement_group.add create_values("items", data.first, required_keys, true)
116
+
117
+ statement_group
118
+ end
119
+
120
+ def detect_required(collection)
121
+ @brute_search.find_required
122
+ rescue NoMethodError
123
+ collection.keys if collection.respond_to?(:keys)
124
+ end
125
+
126
+ def result
127
+ @buffer.string
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,46 @@
1
+ require 'jsonpath'
2
+
3
+ module JSON
4
+ class SchemaGenerator
5
+ class BruteForceRequiredSearch
6
+ def initialize(data)
7
+ @data = data.dup
8
+ @json_path = data.is_a?(Array) ? ['$[*]'] : ['$']
9
+ end
10
+
11
+ def push(key, value)
12
+ @json_path.push value.is_a?(Array) ? "#{key}[*]" : key
13
+ end
14
+
15
+ def pop
16
+ @json_path.pop
17
+ end
18
+
19
+ def current_path
20
+ @json_path.join '.'
21
+ end
22
+
23
+ def search_path search_key
24
+ current_path.gsub(/\[\*\]$/, "[?(@.#{search_key})]")
25
+ end
26
+
27
+ def required? child_key
28
+ begin
29
+ JsonPath.new(search_path(child_key)).on(@data).count == JsonPath.new(current_path).on(@data).count
30
+ rescue SyntaxError
31
+ # There are some special characters that can't be handled by JsonPath.
32
+ # e.g. if child key is OS-DCF:diskConfig
33
+ false
34
+ end
35
+ end
36
+
37
+ def child_keys
38
+ JsonPath.new(current_path).on(@data).map(&:keys).flatten.uniq
39
+ end
40
+
41
+ def find_required
42
+ child_keys.select {|k| required? k}
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,26 @@
1
+ module JSON
2
+ class SchemaGenerator
3
+ class StatementGroup
4
+ def initialize key = nil
5
+ @key = key
6
+ @statements = []
7
+ end
8
+
9
+ def add statement
10
+ @statements << statement
11
+ end
12
+
13
+ def to_s
14
+ buffer = StringIO.new
15
+ if @key.nil?
16
+ buffer.puts "{"
17
+ else
18
+ buffer.puts "\"#{@key}\": {"
19
+ end
20
+ buffer.puts @statements.join ', '
21
+ buffer.puts "}"
22
+ buffer.string
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ module JSON
2
+ class SchemaGenerator
3
+ VERSION = "0.0.9"
4
+ end
5
+ end
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+
5
+ class JSON::SchemaGeneratorCLI
6
+ def initialize(argv, stdin=STDIN, stdout=STDOUT, stderr=STDERR, kernel=Kernel)
7
+ @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
8
+ end
9
+
10
+ def execute!
11
+ default_version = 'draft4'
12
+ supported_versions = ['draft4']
13
+
14
+ options = {
15
+ :schema_version => default_version,
16
+ :defaults => false
17
+ }
18
+
19
+
20
+
21
+ OptionParser.new do |opts|
22
+ opts.on("--defaults", "Record default values in the generated schema") { options[:defaults] = true }
23
+ opts.on("--schema-version draft4", [:draft4],
24
+ "Version of json-schema to generate (#{supported_versions.join ', '}). Default: #{default_version}") do |schema_version|
25
+ options[:schema_version] = schema_version
26
+ end
27
+ opts.on("--allow-null", "Includes 'null' as an allow type for all properties in the schema") { options[:allow_null] = true }
28
+ opts.parse!
29
+ end
30
+
31
+ file = ARGV.shift
32
+ schema = JSON.parse(JSON::SchemaGenerator.generate file, File.read(file), options)
33
+ @stdout.puts JSON.pretty_generate schema
34
+ @kernel.exit(0)
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ file=$1
3
+ bundle exec json-schema-generator $1 --schema-version draft4 --defaults
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ file=$1
3
+ bundle exec json-schema-generator $1 --schema-version draft3
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ file=$1
3
+ bundle exec json-schema-generator $1
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json-schema-generator2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.9
5
+ platform: ruby
6
+ authors:
7
+ - Max Lincoln
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-09-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jsonpath
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: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
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: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: json-schema
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: travis
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
+ description: A very basic json-schema generator
98
+ email:
99
+ - max@devopsy.com
100
+ executables:
101
+ - json-schema-generator
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".gitmodules"
107
+ - ".travis.yml"
108
+ - Gemfile
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - bin/json-schema-generator
113
+ - json-schema-generator.gemspec
114
+ - lib/json-schema-generator.rb
115
+ - lib/json/schema_generator.rb
116
+ - lib/json/schema_generator/brute_force_required_search.rb
117
+ - lib/json/schema_generator/statement_group.rb
118
+ - lib/json/schema_generator/version.rb
119
+ - lib/json/schema_generator_cli.rb
120
+ - scripts/challenges/generate_defaults
121
+ - scripts/challenges/generate_draft3
122
+ - scripts/challenges/generate_draft4
123
+ homepage: ''
124
+ licenses:
125
+ - MIT
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubygems_version: 3.0.8
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: A very basic json-schema generator
146
+ test_files: []