skeleton 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +140 -65
  4. data/Rakefile +10 -3
  5. data/lib/skeleton.rb +6 -10
  6. data/lib/skeleton/contact.rb +1 -13
  7. data/lib/skeleton/error.rb +4 -0
  8. data/lib/skeleton/graph.rb +56 -0
  9. data/lib/skeleton/header.rb +2 -63
  10. data/lib/skeleton/items.rb +34 -0
  11. data/lib/skeleton/license.rb +1 -12
  12. data/lib/skeleton/model.rb +13 -17
  13. data/lib/skeleton/operation.rb +50 -121
  14. data/lib/skeleton/parameter.rb +31 -88
  15. data/lib/skeleton/parameters.rb +40 -0
  16. data/lib/skeleton/path.rb +42 -80
  17. data/lib/skeleton/presenter.rb +19 -0
  18. data/lib/skeleton/property.rb +6 -0
  19. data/lib/skeleton/response.rb +24 -45
  20. data/lib/skeleton/schema.rb +80 -63
  21. data/lib/skeleton/scope.rb +18 -0
  22. data/lib/skeleton/security_scheme.rb +19 -37
  23. data/lib/skeleton/serializers/options.rb +215 -0
  24. data/lib/skeleton/serializers/swagger.rb +197 -0
  25. data/lib/skeleton/structure.rb +92 -138
  26. data/lib/skeleton/swagger.rb +9 -0
  27. data/lib/skeleton/tag.rb +11 -16
  28. data/lib/skeleton/version.rb +1 -1
  29. data/skeleton.gemspec +2 -0
  30. data/test/fixtures/json-schema-draft-04.json +150 -0
  31. data/test/fixtures/schema.json +1482 -0
  32. data/test/integrations/validate_complex_schema_spec.rb +42 -0
  33. data/test/skeleton/graph_test.rb +22 -0
  34. data/test/skeleton/mapper_test.rb +84 -0
  35. data/test/skeleton/operation_test.rb +11 -0
  36. data/test/skeleton/parameter_test.rb +34 -0
  37. data/test/skeleton/parameters_test.rb +9 -0
  38. data/test/skeleton/path_test.rb +46 -0
  39. data/test/skeleton/property_test.rb +8 -0
  40. data/test/skeleton/serializers/options_test.rb +68 -0
  41. data/test/skeleton/serializers/swagger_test.rb +30 -0
  42. data/test/support/factories/structure_factory.rb +86 -0
  43. data/test/support/fixtures.rb +6 -0
  44. data/test/support/kissmetrics/core_api.rb +542 -0
  45. data/{spec/spec_helper.rb → test/test_helper.rb} +7 -1
  46. metadata +73 -25
  47. data/lib/skeleton/config.rb +0 -37
  48. data/lib/skeleton/documentation.rb +0 -17
  49. data/lib/skeleton/example.rb +0 -31
  50. data/lib/skeleton/headers.rb +0 -50
  51. data/lib/skeleton/helpers/controller_helpers.rb +0 -25
  52. data/lib/skeleton/info.rb +0 -40
  53. data/lib/skeleton/item.rb +0 -99
  54. data/lib/skeleton/responses.rb +0 -59
  55. data/lib/skeleton/scopes.rb +0 -24
  56. data/lib/skeleton/security_definitions.rb +0 -46
  57. data/lib/skeleton/security_requirement.rb +0 -29
  58. data/spec/integrations/use_case_spec.rb +0 -131
  59. data/spec/skeleton/operation_spec.rb +0 -113
  60. data/spec/skeleton/serializers/contact_spec.rb +0 -30
  61. data/spec/skeleton/serializers/documentation_spec.rb +0 -23
  62. data/spec/skeleton/serializers/header_spec.rb +0 -57
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bc11d07464eef522de2f6495353a454b7ffc5373
4
- data.tar.gz: edbbaf9c7d4af49d479690a8baa8e8fc9bb01ccb
3
+ metadata.gz: 9294bad43773b066e5ce9b168b882617f614d8a2
4
+ data.tar.gz: ff3a33117c6924c76cec5478162571f44f761c07
5
5
  SHA512:
6
- metadata.gz: 3fd5c0bc49baafa724cb849bbd1ae8a0bab61e0fa6aeb715458bf91909c7f77071841a38533c1120e0b5aff4eed7d9d3f4b277993de4bf591de5e00c4d1e6583
7
- data.tar.gz: f90dbc5845e73cc978acd224d0e5db77b7bf3444ddab5dc9bb51af2f3e99eb495989e4f30644b62a2d08c506311a8f613210e869c5b7661559c27a299b450f4b
6
+ metadata.gz: b22eccf0dbd13780cb453fe23a6c24391dd47de28786b113be1944acc7d5c0e79bc2b2889ec3e5c7d0947b1dc50472fbe28d944bdc5c2a8401e64dfe65136ad4
7
+ data.tar.gz: e8de75ff865e644fcd3e6966b8f541a5710fef50e3f6e95ff3f2d1d80590e36973d465717b904e55ed4c44632bda3d0e933e1b07a03cfc7c44ea79dc3be8ca3a
data/.gitignore CHANGED
@@ -20,3 +20,4 @@ tmp
20
20
  *.o
21
21
  *.a
22
22
  mkmf.log
23
+ .DS_Store
data/README.md CHANGED
@@ -7,88 +7,162 @@ running code.
7
7
 
8
8
  ```ruby
9
9
  # You would put this somewhere such as config/initializers/skeleton.rb
10
- Skeleton.configure do |config|
11
- config.define do |structure|
12
- structure.host = 'api.example.com'
13
- structure.base_path = '/foo'
14
-
15
- structure.schemes = %w(https)
16
- structure.consumes = %(application/json)
17
- structure.produces = %(application/json)
18
- end
10
+ structure = Skeleton.build do |s|
11
+ s.title = 'KISSmetrics API'
12
+ s.version = '1.0.0'
13
+ s.description = 'A simply complex skeleton'
19
14
 
20
- config.info do |info|
21
- info.version = '1.0.6'
22
- info.title = 'An Example API'
23
- info.description = 'An api to interact with data'
24
- info.terms_of_service = 'https://api.example.com/terms/'
25
- end
15
+ s.terms = 'https://www.kissmetrics.com/terms'
26
16
 
27
- config.contact do |contact|
28
- contact.name = 'WarmWaffles'
29
- contact.email = 'warmwaffles@gmail.com'
30
- contact.url = 'https://github.com/warmwaffles/skeleton'
31
- end
17
+ s.contact.name = 'KISSmetrics'
18
+ s.contact.email = 'support@kissmetrics.com'
19
+ s.contact.url = 'https://support.kissmetrics.com'
32
20
 
33
- config.license do |license|
34
- license.name = 'MIT'
35
- end
21
+ s.license.name = 'KISSmetrics'
22
+ s.license.url = 'https://www.kissmetrics.com'
23
+
24
+ s.host = 'api.kissmetrics.com'
25
+ s.base_path = '/core'
26
+ s.scheme(:https)
27
+ s.consume('application/json')
28
+ s.produce('application/json')
36
29
  end
37
30
  ```
38
31
 
39
- Inside of your rails controller you would do the following
32
+ ## Defining models
40
33
 
41
34
  ```ruby
42
- class ApplicationController < ActionController::Base
43
- include Skeleton::Helpers::ControllerHelpers
35
+ structure.define_model('Link') do
36
+ describe('The link object used for api discovery')
37
+ required(:href, type: 'string')
38
+ required(:rel, type: 'string')
39
+ optional(:templated, type: 'boolean')
40
+ optional(:name, type: 'string')
41
+ end
42
+
43
+ structure.define_model('Error') do
44
+ describe('Represents an error')
45
+ required(:message, type: 'string')
46
+ optional(:field, type: 'string')
47
+ optional(:errors, type: 'array', items: { ref: 'Error' })
48
+ end
49
+
50
+ structure.define_model('Meta') do
51
+ describe('The meta information regarding the request')
52
+ required(:status, type: 'integer')
53
+ optional(:message, type: 'string')
54
+ optional(:errors, type: 'array', items: { ref: 'Error' })
44
55
  end
45
56
  ```
46
57
 
47
- Inside a controller that you wish to document do the following
58
+ If you wish to reference a definition from within another model, all you need to
59
+ do is to pass a `:ref`. If you want to know more about this structure, please
60
+ see the swagger documentation.
61
+
62
+ ## Defining paths
48
63
 
49
64
  ```ruby
50
- class AccountsController < ApplicationController
51
- define_api_path('/accounts') do |path|
52
- path.get do |operation|
53
- operation.tag('account')
54
- operation.description = 'List all of the accounts available to you'
55
-
56
- operation.parameter(:query, 'limit') do |p|
57
- p.description = 'maximum number of results to return'
58
- p.type = 'integer'
59
- p.format = 'int32'
60
- p.required = false
61
- end
62
- operation.parameter(:query, 'offset') do |p|
63
- p.description = 'offset within the results returned'
64
- p.type = 'integer'
65
- p.format = 'int32'
66
- p.required = false
67
- end
65
+ skeleton.define_path('/products') do
66
+ get do
67
+ identify('list-products')
68
+ tag('product')
69
+ summarize('List products')
70
+ describe('List all of the products')
71
+
72
+ parameters(:query) do
73
+ optional(:limit, type: 'integer', format: 'int32', default: 20, maximum: 50, minimum: 0)
74
+ optional(:offset, type: 'integer', format: 'int32', default: 0)
75
+ end
76
+
77
+ response(200, default: true) do
78
+ describe('Product list')
79
+ header('Link', type: 'string', description: 'The list of links for the resource')
80
+ schema(ref: 'Product')
81
+ end
82
+
83
+ response(403) do
84
+ describe('Unauthorized')
85
+ header('Link', type: 'string', description: 'The list of links for the resource')
86
+ schema(ref: 'ErrorResponse')
68
87
  end
69
88
  end
70
89
 
71
- define_api_path('/accounts/{account_id}') do |path|
72
- path.get do |operation|
73
- operation.tag('account')
74
- operation.description = 'Get an account'
75
- operation.parameter(:query, 'account_id') do |p|
76
- p.description = 'The account id'
77
- p.type = 'string'
78
- p.format = 'uuid'
79
- p.required = true
80
- end
90
+ head do
91
+ tag('product')
92
+ summarize('List products headers')
93
+ describe('List the headers for the products action')
94
+
95
+ parameters(:query) do
96
+ optional(:limit, type: 'integer', format: 'int32', default: 20, maximum: 50, minimum: 0)
97
+ optional(:offset, type: 'integer', format: 'int32', default: 0)
81
98
  end
82
99
 
83
- path.options do |operation|
84
- operation.tag('account')
85
- operation.description = 'Show available options for the account'
86
- operation.parameter(:query, 'account_id') do |p|
87
- p.description = 'The account id'
88
- p.type = 'string'
89
- p.format = 'uuid'
90
- p.required = true
91
- end
100
+ response(200, default: true) do
101
+ describe('Product list')
102
+ header('Link', type: 'string', description: 'The list of links for the resource')
103
+ end
104
+
105
+ response(403) do
106
+ describe('Unauthorized')
107
+ header('Link', type: 'string', description: 'The list of links for the resource')
108
+ end
109
+ end
110
+
111
+ post do
112
+ tag('product')
113
+ summarize('Create product')
114
+ describe('Create a product')
115
+
116
+ parameters(:body) do
117
+ required(:body, schema: { ref: 'NewProduct' })
118
+ end
119
+
120
+ response(201) do
121
+ describe('Product created')
122
+ header('Link', type: 'string', description: 'The list of links for the resource')
123
+ schema(ref: 'Product')
124
+ end
125
+
126
+ response(400) do
127
+ describe('Client error')
128
+ header('Link', type: 'string', description: 'The list of links for the resource')
129
+ schema(ref: 'ErrorResponse')
130
+ end
131
+
132
+ response(403) do
133
+ describe('Unauthorized')
134
+ header('Link', type: 'string', description: 'The list of links for the resource')
135
+ schema(ref: 'ErrorResponse')
136
+ end
137
+ end
138
+
139
+ options do
140
+ tag('product')
141
+ summarize('List actions for a product')
142
+ describe('List actions for a product')
143
+
144
+ parameters(:query) do
145
+ optional(:limit, type: 'integer', format: 'int32', default: 20, maximum: 50, minimum: 0)
146
+ optional(:offset, type: 'integer', format: 'int32', default: 0)
147
+ end
148
+
149
+ response(200, default: true) do
150
+ describe('Product action list')
151
+ header('Allow', type: 'array', items: { type: 'string' }, collection_format: 'csv')
152
+ header('Link', type: 'string', description: 'The list of links for the resource')
153
+ schema(ref: 'OptionsResponse')
154
+ end
155
+
156
+ response(400) do
157
+ describe('Client error')
158
+ header('Link', type: 'string', description: 'The list of links for the resource')
159
+ schema(ref: 'ErrorResponse')
160
+ end
161
+
162
+ response(403) do
163
+ describe('Unauthorized')
164
+ header('Link', type: 'string', description: 'The list of links for the resource')
165
+ schema(ref: 'ErrorResponse')
92
166
  end
93
167
  end
94
168
  end
@@ -102,7 +176,8 @@ class DocumentationController < ApplicationController
102
176
  def swagger
103
177
  respond_to do |format|
104
178
  format.json do
105
- render(json: Skeleton.config.to_swagger_json, status: 200)
179
+ serializer = Skeleton::Serializers::Swagger.new(my_structure)
180
+ render(json: MultiJson.dump(serializer.to_h), status: 200)
106
181
  end
107
182
  end
108
183
  end
data/Rakefile CHANGED
@@ -2,16 +2,23 @@ require 'bundler/gem_tasks'
2
2
 
3
3
  namespace :test do
4
4
  task :env do
5
- $LOAD_PATH.unshift('lib', 'spec')
5
+ $LOAD_PATH.unshift('lib', 'test')
6
6
  end
7
7
 
8
8
  desc 'Runs only the specs in this project'
9
9
  task :specs => [:env] do
10
- Dir.glob('./spec/**/*_spec.rb') { |f| require f }
10
+ Dir.glob('./test/**/*_spec.rb') { |f| require f }
11
+ end
12
+
13
+ desc 'Runs only the test units in this project'
14
+ task :units => [:env] do
15
+ Dir.glob('./test/**/*_test.rb') { |f| require f }
11
16
  end
12
17
 
13
18
  desc 'Runs all of the tests within this project'
14
- task :all => [:specs]
19
+ task :all => [:env] do
20
+ Dir.glob('./test/**/*_{spec,test}.rb') { |f| require f }
21
+ end
15
22
  end
16
23
 
17
24
  desc 'Runs all of the tests within this project'
@@ -1,20 +1,16 @@
1
1
  require 'skeleton/version'
2
2
  require 'skeleton/structure'
3
- require 'skeleton/config'
4
- require 'skeleton/helpers/controller_helpers'
3
+ require 'skeleton/serializers/swagger'
4
+ require 'skeleton/serializers/options'
5
5
 
6
6
  module Skeleton
7
+ # Allows you to build a skeleton structure from within a block
8
+ #
9
+ # @return [Skeleton::Structure]
7
10
  def self.build(&block)
8
11
  structure = Skeleton::Structure.new
9
12
  yield(structure) if block
10
13
  structure
11
14
  end
12
-
13
- def self.config
14
- @config ||= Skeleton::Config.new
15
- end
16
-
17
- def self.configure(&block)
18
- yield(config) if block
19
- end
20
15
  end
16
+
@@ -1,17 +1,5 @@
1
- require 'skeleton/model'
2
-
3
1
  module Skeleton
4
- class Contact < Model
2
+ class Contact
5
3
  attr_accessor :name, :email, :url
6
- attr_presence :name, :email, :url
7
-
8
- def to_h
9
- hash = {}
10
- hash[:name] = name if name?
11
- hash[:email] = email if email?
12
- hash[:url] = url if url?
13
- hash
14
- end
15
- alias_method :to_swagger_hash, :to_h
16
4
  end
17
5
  end
@@ -0,0 +1,4 @@
1
+ module Skeleton
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,56 @@
1
+ require 'set'
2
+ require 'tsort'
3
+
4
+ module Skeleton
5
+ class Graph
6
+ include Enumerable
7
+ include TSort
8
+
9
+ def initialize
10
+ @dep = Hash.new { |h,k| h[k] = Set.new }
11
+ end
12
+
13
+ # Register a dependency
14
+ #
15
+ # @param base [Object] the base object
16
+ # @param list [Object,Array] list of dependents
17
+ def register(base, *list)
18
+ dependents = Array(list).flatten
19
+ @dep[base] ||= Set.new
20
+ @dep[base].merge(dependents)
21
+ end
22
+
23
+ # Iterate over each of the nodes in this graph with their edge
24
+ #
25
+ # @return [void]
26
+ def each(&block)
27
+ @dep.each do |base, set|
28
+ set.each do |name|
29
+ yield(base, name) if block
30
+ end
31
+ end
32
+ end
33
+
34
+ # Takes all of the nodes in this graph and creates a set with them
35
+ #
36
+ # @return [Set]
37
+ def to_set
38
+ set = Set.new
39
+ @dep.each do |base, deps|
40
+ set.add(base)
41
+ set.merge(deps)
42
+ end
43
+ set
44
+ end
45
+
46
+ def each_dependent_for(base, &block)
47
+ each_strongly_connected_component_from(base) do |dependent, _|
48
+ yield(dependent)
49
+ end
50
+ end
51
+
52
+ def tsort_each_child(node, &block)
53
+ @dep[node].each(&block)
54
+ end
55
+ end
56
+ end
@@ -1,67 +1,6 @@
1
- require 'skeleton/model'
1
+ require 'skeleton/schema'
2
2
 
3
3
  module Skeleton
4
- class Header < Model
5
- attr_accessor :type, :format, :title, :description, :default, :multiple_of,
6
- :maximum, :exclusive_maximum, :minimum, :exclusive_minimum,
7
- :max_length, :min_length, :pattern, :max_items, :min_items,
8
- :unique_items, :max_properties, :min_properties
9
-
10
- attr_writer :enum
11
- attr_presence :exclusive_maximum, :exclusive_minimum, :unique_items
12
-
13
- def enum
14
- @enum ||= []
15
- end
16
-
17
- def enum?
18
- !enum.empty?
19
- end
20
-
21
- def to_h
22
- hash = {}
23
- hash[:description] = header.description if header.description?
24
- hash[:type] = header.type if header.type?
25
- hash[:format] = header.format if header.format?
26
- hash[:items] = header.items if header.items?
27
- hash[:collection_format] = header.collection_format if header.collection_format?
28
- hash[:default] = header.default if header.default?
29
- hash[:maximum] = header.maximum if header.maximum?
30
- hash[:exclusive_maximum] = header.exclusive_maximum if header.exclusive_maximum?
31
- hash[:minimum] = header.minimum if header.minimum?
32
- hash[:exclusive_minimum] = header.exclusive_minimum if header.exclusive_minimum?
33
- hash[:max_length] = header.max_length if header.max_length?
34
- hash[:min_length] = header.min_length if header.min_length?
35
- hash[:pattern] = header.pattern if header.pattern?
36
- hash[:max_items] = header.max_items if header.max_items?
37
- hash[:min_items] = header.min_items if header.min_items?
38
- hash[:unique_items] = header.unique_items if header.unique_items?
39
- hash[:enum] = header.enum if header.enum?
40
- hash[:multiple_of] = header.multiple_of if header.multiple_of?
41
- hash
42
- end
43
-
44
- def to_swagger_hash
45
- hash = {}
46
- hash[:description] = header.description if header.description?
47
- hash[:type] = header.type if header.type?
48
- hash[:format] = header.format if header.format?
49
- hash[:items] = header.items if header.items?
50
- hash[:collectionFormat] = header.collection_format if header.collection_format?
51
- hash[:default] = header.default if header.default?
52
- hash[:maximum] = header.maximum if header.maximum?
53
- hash[:exclusiveMaximum] = header.exclusive_maximum if header.exclusive_maximum?
54
- hash[:minimum] = header.minimum if header.minimum?
55
- hash[:exclusiveMinimum] = header.exclusive_minimum if header.exclusive_minimum?
56
- hash[:maxLength] = header.max_length if header.max_length?
57
- hash[:minLength] = header.min_length if header.min_length?
58
- hash[:pattern] = header.pattern if header.pattern?
59
- hash[:maxItems] = header.max_items if header.max_items?
60
- hash[:minItems] = header.min_items if header.min_items?
61
- hash[:uniqueItems] = header.unique_items if header.unique_items?
62
- hash[:enum] = header.enum if header.enum?
63
- hash[:multipleOf] = header.multiple_of if header.multiple_of?
64
- hash
65
- end
4
+ class Header < Schema
66
5
  end
67
6
  end