praxis 2.0.pre.8 → 2.0.pre.13

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 (242) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +1 -3
  5. data/CHANGELOG.md +33 -0
  6. data/TODO.md +1 -4
  7. data/bin/praxis +67 -12
  8. data/lib/praxis.rb +10 -3
  9. data/lib/praxis/action_definition.rb +15 -13
  10. data/lib/praxis/action_definition/headers_dsl_compiler.rb +0 -7
  11. data/lib/praxis/api_general_info.rb +1 -1
  12. data/lib/praxis/application.rb +6 -2
  13. data/lib/praxis/blueprint.rb +357 -0
  14. data/lib/praxis/bootloader.rb +9 -3
  15. data/lib/praxis/bootloader_stages/environment.rb +16 -13
  16. data/lib/praxis/collection.rb +1 -11
  17. data/lib/praxis/config_hash.rb +44 -0
  18. data/lib/praxis/docs/{openapi → open_api}/info_object.rb +18 -10
  19. data/lib/praxis/docs/{openapi → open_api}/media_type_object.rb +0 -0
  20. data/lib/praxis/docs/{openapi → open_api}/operation_object.rb +0 -0
  21. data/lib/praxis/docs/{openapi → open_api}/parameter_object.rb +0 -0
  22. data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +0 -0
  23. data/lib/praxis/docs/{openapi → open_api}/request_body_object.rb +0 -0
  24. data/lib/praxis/docs/{openapi → open_api}/response_object.rb +0 -0
  25. data/lib/praxis/docs/{openapi → open_api}/responses_object.rb +0 -0
  26. data/lib/praxis/docs/{openapi → open_api}/schema_object.rb +0 -0
  27. data/lib/praxis/docs/{openapi → open_api}/server_object.rb +0 -0
  28. data/lib/praxis/docs/{openapi → open_api}/tag_object.rb +0 -0
  29. data/lib/praxis/docs/open_api_generator.rb +91 -6
  30. data/lib/praxis/endpoint_definition.rb +273 -0
  31. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +182 -58
  32. data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +3 -2
  33. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +47 -56
  34. data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +153 -0
  35. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +20 -8
  36. data/lib/praxis/extensions/field_expansion.rb +3 -36
  37. data/lib/praxis/extensions/pagination.rb +5 -32
  38. data/lib/praxis/extensions/pagination/ordering_params.rb +1 -1
  39. data/lib/praxis/extensions/pagination/pagination_params.rb +6 -4
  40. data/lib/praxis/field_expander.rb +90 -0
  41. data/lib/praxis/finalizable.rb +34 -0
  42. data/lib/praxis/mapper/active_model_compat.rb +4 -0
  43. data/lib/praxis/mapper/resource.rb +18 -2
  44. data/lib/praxis/mapper/selector_generator.rb +2 -1
  45. data/lib/praxis/mapper/sequel_compat.rb +7 -0
  46. data/lib/praxis/media_type.rb +3 -68
  47. data/lib/praxis/plugin_concern.rb +1 -1
  48. data/lib/praxis/plugins/mapper_plugin.rb +24 -15
  49. data/lib/praxis/plugins/pagination_plugin.rb +34 -4
  50. data/lib/praxis/renderer.rb +88 -0
  51. data/lib/praxis/request.rb +1 -1
  52. data/lib/praxis/resource_definition.rb +2 -311
  53. data/lib/praxis/response_definition.rb +2 -10
  54. data/lib/praxis/response_template.rb +3 -3
  55. data/lib/praxis/router.rb +2 -2
  56. data/lib/praxis/routing_config.rb +1 -1
  57. data/lib/praxis/tasks/api_docs.rb +17 -64
  58. data/lib/praxis/tasks/routes.rb +1 -1
  59. data/lib/praxis/types/media_type_common.rb +1 -11
  60. data/lib/praxis/version.rb +1 -1
  61. data/praxis.gemspec +0 -1
  62. data/spec/functional_spec.rb +5 -9
  63. data/spec/praxis/action_definition_spec.rb +12 -20
  64. data/spec/praxis/blueprint_spec.rb +373 -0
  65. data/spec/praxis/bootloader_spec.rb +10 -2
  66. data/spec/praxis/collection_spec.rb +0 -13
  67. data/spec/praxis/config_hash_spec.rb +64 -0
  68. data/spec/praxis/{resource_definition_spec.rb → endpoint_definition_spec.rb} +37 -64
  69. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +249 -168
  70. data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +25 -6
  71. data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +190 -8
  72. data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +140 -0
  73. data/spec/praxis/extensions/field_expansion_spec.rb +5 -24
  74. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +1 -1
  75. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +1 -1
  76. data/spec/praxis/extensions/support/spec_resources_active_model.rb +1 -1
  77. data/spec/praxis/field_expander_spec.rb +149 -0
  78. data/spec/praxis/mapper/selector_generator_spec.rb +1 -1
  79. data/spec/praxis/media_type_identifier_spec.rb +5 -4
  80. data/spec/praxis/media_type_spec.rb +4 -93
  81. data/spec/praxis/renderer_spec.rb +188 -0
  82. data/spec/praxis/response_definition_spec.rb +0 -31
  83. data/spec/praxis/response_spec.rb +1 -1
  84. data/spec/praxis/router_spec.rb +8 -8
  85. data/spec/praxis/routing_config_spec.rb +3 -3
  86. data/spec/spec_app/app/controllers/instances.rb +13 -7
  87. data/spec/spec_app/design/media_types/instance.rb +1 -19
  88. data/spec/spec_app/design/media_types/volume.rb +1 -1
  89. data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -14
  90. data/spec/spec_app/design/resources/instances.rb +5 -8
  91. data/spec/spec_app/design/resources/volume_snapshots.rb +1 -1
  92. data/spec/spec_app/design/resources/volumes.rb +1 -1
  93. data/spec/support/spec_authorization_plugin.rb +1 -1
  94. data/spec/support/spec_blueprints.rb +72 -0
  95. data/spec/support/{spec_resource_definitions.rb → spec_endpoint_definitions.rb} +2 -2
  96. data/spec/support/spec_media_types.rb +6 -26
  97. data/tasks/thor/app.rb +8 -34
  98. data/tasks/thor/example.rb +51 -285
  99. data/tasks/thor/model.rb +40 -0
  100. data/tasks/thor/scaffold.rb +117 -0
  101. data/tasks/thor/templates/generator/empty_app/.gitignore +0 -1
  102. data/tasks/thor/templates/generator/empty_app/Gemfile +7 -23
  103. data/tasks/thor/templates/generator/empty_app/README.md +1 -1
  104. data/tasks/thor/templates/generator/empty_app/Rakefile +4 -13
  105. data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.empty_directory +0 -0
  106. data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.gitkeep +0 -0
  107. data/tasks/thor/templates/generator/empty_app/config/environment.rb +26 -17
  108. data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.empty_directory +0 -0
  109. data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.gitkeep +0 -0
  110. data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.empty_directory +0 -0
  111. data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.gitkeep +0 -0
  112. data/tasks/thor/templates/generator/empty_app/docs/.empty_directory +0 -0
  113. data/tasks/thor/templates/generator/empty_app/docs/.gitkeep +0 -0
  114. data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +14 -9
  115. data/tasks/thor/templates/generator/example_app/.gitignore +1 -0
  116. data/tasks/thor/templates/generator/example_app/Gemfile +19 -0
  117. data/tasks/thor/templates/generator/example_app/Rakefile +61 -0
  118. data/tasks/thor/templates/generator/example_app/app/models/user.rb +6 -0
  119. data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +24 -0
  120. data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +17 -0
  121. data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +11 -0
  122. data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +25 -0
  123. data/tasks/thor/templates/generator/example_app/config.ru +30 -0
  124. data/tasks/thor/templates/generator/example_app/config/environment.rb +41 -0
  125. data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +12 -0
  126. data/tasks/thor/templates/generator/example_app/db/seeds.rb +6 -0
  127. data/tasks/thor/templates/generator/example_app/design/api.rb +18 -0
  128. data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +37 -0
  129. data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +21 -0
  130. data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +20 -0
  131. data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +42 -0
  132. data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +37 -0
  133. data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +98 -0
  134. data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +18 -0
  135. data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +77 -0
  136. data/tasks/thor/templates/generator/scaffold/implementation/resources/base.rb +11 -0
  137. data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +45 -0
  138. data/tasks/thor/templates/generator/scaffold/models/active_record.rb +6 -0
  139. data/tasks/thor/templates/generator/scaffold/models/sequel.rb +6 -0
  140. metadata +64 -136
  141. data/lib/api_browser/.bowerrc +0 -3
  142. data/lib/api_browser/.editorconfig +0 -21
  143. data/lib/api_browser/Gruntfile.js +0 -581
  144. data/lib/api_browser/app/index.html +0 -59
  145. data/lib/api_browser/app/js/app.js +0 -48
  146. data/lib/api_browser/app/js/controllers/action.js +0 -47
  147. data/lib/api_browser/app/js/controllers/controller.js +0 -10
  148. data/lib/api_browser/app/js/controllers/menu.js +0 -93
  149. data/lib/api_browser/app/js/controllers/trait.js +0 -10
  150. data/lib/api_browser/app/js/controllers/type.js +0 -24
  151. data/lib/api_browser/app/js/directives/attribute_description.js +0 -56
  152. data/lib/api_browser/app/js/directives/attribute_table.js +0 -28
  153. data/lib/api_browser/app/js/directives/conditional_requirements.js +0 -13
  154. data/lib/api_browser/app/js/directives/fixed_if_fits.js +0 -38
  155. data/lib/api_browser/app/js/directives/highlight.js +0 -14
  156. data/lib/api_browser/app/js/directives/menu_item.js +0 -59
  157. data/lib/api_browser/app/js/directives/no_container.js +0 -8
  158. data/lib/api_browser/app/js/directives/readable_list.js +0 -87
  159. data/lib/api_browser/app/js/directives/request_examples.js +0 -31
  160. data/lib/api_browser/app/js/directives/type_placeholder.js +0 -30
  161. data/lib/api_browser/app/js/directives/url.js +0 -15
  162. data/lib/api_browser/app/js/factories/Configuration.js +0 -12
  163. data/lib/api_browser/app/js/factories/Documentation.js +0 -61
  164. data/lib/api_browser/app/js/factories/Example.js +0 -51
  165. data/lib/api_browser/app/js/factories/PageInfo.js +0 -9
  166. data/lib/api_browser/app/js/factories/normalize_attributes.js +0 -20
  167. data/lib/api_browser/app/js/factories/prepare_template.js +0 -15
  168. data/lib/api_browser/app/js/factories/template_for.js +0 -128
  169. data/lib/api_browser/app/js/filters/attribute_name.js +0 -10
  170. data/lib/api_browser/app/js/filters/friendly_json.js +0 -5
  171. data/lib/api_browser/app/js/filters/has_requirement.js +0 -14
  172. data/lib/api_browser/app/js/filters/header_info.js +0 -9
  173. data/lib/api_browser/app/js/filters/is_empty.js +0 -8
  174. data/lib/api_browser/app/js/filters/markdown.js +0 -6
  175. data/lib/api_browser/app/js/filters/resource_name.js +0 -5
  176. data/lib/api_browser/app/js/filters/tag_requirement.js +0 -13
  177. data/lib/api_browser/app/sass/modules/_body.scss +0 -40
  178. data/lib/api_browser/app/sass/modules/_cloke.scss +0 -8
  179. data/lib/api_browser/app/sass/modules/_header.scss +0 -10
  180. data/lib/api_browser/app/sass/modules/_nav.scss +0 -7
  181. data/lib/api_browser/app/sass/modules/_sidebar.scss +0 -134
  182. data/lib/api_browser/app/sass/modules/_switch.scss +0 -55
  183. data/lib/api_browser/app/sass/modules/_table.scss +0 -13
  184. data/lib/api_browser/app/sass/praxis.scss +0 -70
  185. data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +0 -774
  186. data/lib/api_browser/app/views/action.html +0 -97
  187. data/lib/api_browser/app/views/builtin/field-selector.html +0 -24
  188. data/lib/api_browser/app/views/controller.html +0 -55
  189. data/lib/api_browser/app/views/directives/attribute_description.html +0 -2
  190. data/lib/api_browser/app/views/directives/attribute_description/default.html +0 -2
  191. data/lib/api_browser/app/views/directives/attribute_description/example.html +0 -13
  192. data/lib/api_browser/app/views/directives/attribute_description/headers.html +0 -8
  193. data/lib/api_browser/app/views/directives/attribute_description/member_options.html +0 -4
  194. data/lib/api_browser/app/views/directives/attribute_description/values.html +0 -14
  195. data/lib/api_browser/app/views/directives/attribute_table.html +0 -17
  196. data/lib/api_browser/app/views/directives/menu_item.html +0 -8
  197. data/lib/api_browser/app/views/directives/url.html +0 -3
  198. data/lib/api_browser/app/views/examples/general.html +0 -26
  199. data/lib/api_browser/app/views/home.html +0 -5
  200. data/lib/api_browser/app/views/layout.html +0 -8
  201. data/lib/api_browser/app/views/menu.html +0 -42
  202. data/lib/api_browser/app/views/navbar.html +0 -9
  203. data/lib/api_browser/app/views/trait.html +0 -13
  204. data/lib/api_browser/app/views/type.html +0 -6
  205. data/lib/api_browser/app/views/type/details.html +0 -33
  206. data/lib/api_browser/app/views/types/embedded/array.html +0 -2
  207. data/lib/api_browser/app/views/types/embedded/default.html +0 -12
  208. data/lib/api_browser/app/views/types/embedded/field-selector.html +0 -13
  209. data/lib/api_browser/app/views/types/embedded/links.html +0 -11
  210. data/lib/api_browser/app/views/types/embedded/requirements.html +0 -6
  211. data/lib/api_browser/app/views/types/embedded/single_req.html +0 -9
  212. data/lib/api_browser/app/views/types/embedded/struct.html +0 -14
  213. data/lib/api_browser/app/views/types/label/link.html +0 -1
  214. data/lib/api_browser/app/views/types/label/primitive.html +0 -1
  215. data/lib/api_browser/app/views/types/label/primitive_collection.html +0 -1
  216. data/lib/api_browser/app/views/types/label/type.html +0 -1
  217. data/lib/api_browser/app/views/types/label/type_collection.html +0 -1
  218. data/lib/api_browser/app/views/types/main/array.html +0 -22
  219. data/lib/api_browser/app/views/types/main/default.html +0 -23
  220. data/lib/api_browser/app/views/types/main/hash.html +0 -23
  221. data/lib/api_browser/app/views/types/standalone/array.html +0 -3
  222. data/lib/api_browser/app/views/types/standalone/default.html +0 -18
  223. data/lib/api_browser/app/views/types/standalone/struct.html +0 -2
  224. data/lib/api_browser/bower_template.json +0 -41
  225. data/lib/api_browser/package-lock.json +0 -7110
  226. data/lib/api_browser/package.json +0 -43
  227. data/lib/praxis/docs/generator.rb +0 -243
  228. data/lib/praxis/docs/link_builder.rb +0 -30
  229. data/lib/praxis/links.rb +0 -135
  230. data/lib/praxis/types/multipart.rb +0 -109
  231. data/spec/api_browser/directives/type_placeholder_spec.js +0 -134
  232. data/spec/api_browser/factories/configuration_spec.js +0 -32
  233. data/spec/api_browser/factories/documentation_spec.js +0 -100
  234. data/spec/api_browser/factories/normalize_attributes_spec.js +0 -92
  235. data/spec/api_browser/factories/template_for_spec.js +0 -67
  236. data/spec/api_browser/filters/attribute_name_spec.js +0 -23
  237. data/spec/praxis/types/multipart_spec.rb +0 -112
  238. data/tasks/thor/templates/generator/empty_app/.rspec +0 -1
  239. data/tasks/thor/templates/generator/empty_app/Guardfile +0 -3
  240. data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +0 -57
  241. data/tasks/thor/templates/generator/empty_app/docs/app.js +0 -1
  242. data/tasks/thor/templates/generator/empty_app/docs/styles.scss +0 -3
@@ -48,7 +48,7 @@ module Praxis
48
48
 
49
49
  after(:app) do
50
50
  Praxis::Blueprint.finalize!
51
- Praxis::ResourceDefinition.finalize!
51
+ Praxis::EndpointDefinition.finalize!
52
52
  end
53
53
 
54
54
  end
@@ -66,12 +66,18 @@ module Praxis
66
66
 
67
67
  def before(*stage_path, &block)
68
68
  stage_name = stage_path.shift
69
- stages.find { |stage| stage.name == stage_name }.before(*stage_path, &block)
69
+ the_stage = stages.find { |stage| stage.name == stage_name }
70
+ raise Exceptions::StageNotFound.new("Error running a before block for stage #{stage_name}") unless the_stage
71
+
72
+ the_stage.before(*stage_path, &block)
70
73
  end
71
74
 
72
75
  def after(*stage_path, &block)
73
76
  stage_name = stage_path.shift
74
- stages.find { |stage| stage.name == stage_name }.after(*stage_path, &block)
77
+ the_stage = stages.find { |stage| stage.name == stage_name }
78
+ raise Exceptions::StageNotFound.new("Error running an after block for stage #{stage_name}") unless the_stage
79
+
80
+ the_stage.after(*stage_path, &block)
75
81
  end
76
82
 
77
83
  def use(plugin,**options, &block)
@@ -20,19 +20,22 @@ module Praxis
20
20
 
21
21
  def setup_default_layout!
22
22
  application.layout do
23
- layout do
24
- map :initializers, 'config/initializers/**/*'
25
- map :lib, 'lib/**/*'
26
- map :design, 'design/' do
27
- map :api, 'api.rb'
28
- map :media_types, '**/media_types/**/*'
29
- map :resources, '**/resources/**/*'
30
- end
31
- map :app, 'app/' do
32
- map :models, 'models/**/*'
33
- map :controllers, '**/controllers/**/*'
34
- map :responses, '**/responses/**/*'
35
- end
23
+ map :initializers, 'config/initializers/**/*'
24
+ map :lib, 'lib/**/*'
25
+ map :design, 'design/' do
26
+ map :api, 'api.rb'
27
+ map :helpers, '**/helpers/**/*'
28
+ map :types, '**/types/**/*'
29
+ map :media_types, '**/media_types/**/*'
30
+ map :endpoints, '**/endpoints/**/*'
31
+ end
32
+ map :app, 'app/' do
33
+ map :models, 'models/**/*'
34
+ map :responses, '**/responses/**/*'
35
+ map :exceptions, '**/exceptions/**/*'
36
+ map :concerns, '**/concerns/**/*'
37
+ map :resources, '**/resources/**/*'
38
+ map :controllers, '**/controllers/**/*'
36
39
  end
37
40
  end
38
41
  end
@@ -14,8 +14,7 @@ module Praxis
14
14
  klass.member_type type
15
15
  type.const_set :Collection, klass
16
16
  else
17
- warn "DEPRECATION: Praxis::Collection.of() for non-MediaTypes will be unsupported in 1.0. Use Attributor::Collection.of() instead."
18
- Attributor::Collection.of(type)
17
+ raise "Praxis::Collection.of() for non-MediaTypes is unsupported. Use Attributor::Collection.of() instead."
19
18
  end
20
19
 
21
20
  end
@@ -23,21 +22,12 @@ module Praxis
23
22
  def self.member_type(type=nil)
24
23
  unless type.nil?
25
24
  @member_type = type
26
- @views = nil
27
25
  self.identifier(type.identifier + ';type=collection') unless type.identifier.nil?
28
26
  end
29
27
 
30
28
  @member_type
31
29
  end
32
30
 
33
- def self.views
34
- @views ||= begin
35
- @member_type.views.each_with_object(Hash.new) do |(name, view), hash|
36
- hash[name] = Praxis::CollectionView.new(name, @member_type, view)
37
- end
38
- end
39
- end
40
-
41
31
  def self.domain_model
42
32
  @member_type.domain_model
43
33
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ # TODO: move to Praxis proper...it's not used here
3
+ module Praxis
4
+ class ConfigHash < BasicObject
5
+ attr_reader :hash
6
+
7
+ def self.from(hash = {}, &block)
8
+ new(hash, &block)
9
+ end
10
+
11
+ def initialize(hash = {}, &block)
12
+ @hash = hash
13
+ @block = block
14
+ end
15
+
16
+ def to_hash
17
+ instance_eval(&@block)
18
+ @hash
19
+ end
20
+
21
+ def respond_to_missing?(_method_name, _include_private = false)
22
+ true
23
+ end
24
+
25
+ def method_missing(name, value, *rest, &block) # rubocop:disable Style/MethodMissing
26
+ if (existing = @hash[name])
27
+ if block
28
+ existing << [value, block]
29
+ else
30
+ existing << value
31
+ rest.each do |v|
32
+ existing << v
33
+ end
34
+ end
35
+ else
36
+ @hash[name] = if rest.any?
37
+ [value] + rest
38
+ else
39
+ value
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -10,20 +10,28 @@ module Praxis
10
10
  end
11
11
 
12
12
  def dump
13
- data ={
14
- title: info.title,
15
- description: info.description,
16
- termsOfService: info.termsOfService,
17
- contact: info.contact,
18
- license: info.license,
19
- version: version,
20
- :'x-name' => info.name,
21
- :'x-logo' => {
13
+ data = { version: version }
14
+ [
15
+ :title,
16
+ :description,
17
+ :termsOfService,
18
+ :contact,
19
+ :license
20
+ ].each do |attr|
21
+ val = info.send(attr)
22
+ data[attr] = val if val
23
+ end
24
+
25
+ # Special attributes
26
+ data[:'x-name'] = info.name
27
+ if info.logo_url
28
+ data[:'x-logo'] = {
22
29
  url: info.logo_url,
23
30
  backgroundColor: "#FFFFFF",
24
31
  altText: info.title
25
32
  }
26
- }
33
+ end
34
+ data
27
35
  end
28
36
  end
29
37
  end
File without changes
@@ -1,13 +1,31 @@
1
- require_relative 'openapi/info_object.rb'
2
- require_relative 'openapi/server_object.rb'
3
- require_relative 'openapi/paths_object.rb'
4
- require_relative 'openapi/tag_object.rb'
1
+ require_relative 'open_api/info_object.rb'
2
+ require_relative 'open_api/server_object.rb'
3
+ require_relative 'open_api/paths_object.rb'
4
+ require_relative 'open_api/tag_object.rb'
5
5
 
6
6
  module Praxis
7
7
  module Docs
8
8
 
9
- class OpenApiGenerator < Generator
9
+ class OpenApiGenerator
10
+ require 'active_support/core_ext/enumerable' # For index_by
11
+
10
12
  API_DOCS_DIRNAME = 'docs/openapi'
13
+ EXCLUDED_TYPES_FROM_OUTPUT = Set.new([
14
+ Attributor::Boolean,
15
+ Attributor::CSV,
16
+ Attributor::DateTime,
17
+ Attributor::Date,
18
+ Attributor::Float,
19
+ Attributor::Hash,
20
+ Attributor::Ids,
21
+ Attributor::Integer,
22
+ Attributor::Object,
23
+ Attributor::String,
24
+ Attributor::Symbol,
25
+ Attributor::URI,
26
+ ]).freeze
27
+
28
+ attr_reader :resources_by_version, :types_by_id, :infos_by_version, :doc_root_dir
11
29
 
12
30
  # substitutes ":params_like_so" for {params_like_so}
13
31
  def self.templatize_url( string )
@@ -37,10 +55,77 @@ module Praxis
37
55
 
38
56
  private
39
57
 
58
+ def collect_resources
59
+ # load all resource definitions registered with Praxis
60
+ Praxis::Application.instance.endpoint_definitions.map do |resource|
61
+ # skip resources with doc_visibility of :none
62
+ next if resource.metadata[:doc_visibility] == :none
63
+ version = resource.version
64
+ # TODO: it seems that we shouldn't hardcode n/a in Praxis
65
+ # version = "unversioned" if version == "n/a"
66
+ @resources_by_version[version] << resource
67
+ end
68
+ end
69
+
70
+ def collect_types
71
+ @types_by_id = ObjectSpace.each_object( Class ).select do |obj|
72
+ obj < Attributor::Type
73
+ end.index_by(&:id)
74
+ end
75
+
40
76
  def write_index_file( for_versions: )
41
77
  # TODO. create a simple html file that can link to the individual versions available
42
78
  end
43
79
 
80
+ def scan_types_for_version(version, dumped_resources)
81
+ found_media_types = resources_by_version[version].select{|r| r.media_type}.collect {|r| r.media_type.describe }
82
+
83
+ # We'll start by processing the rendered mediatypes
84
+ processed_types = Set.new(resources_by_version[version].select do|r|
85
+ r.media_type && !r.media_type.is_a?(Praxis::SimpleMediaType)
86
+ end.collect(&:media_type))
87
+
88
+ newfound = Set.new
89
+ found_media_types.each do |mt|
90
+ newfound += scan_dump_for_types( { type: mt} , processed_types )
91
+ end
92
+ # Then will process the rendered resources (noting)
93
+ newfound += scan_dump_for_types( dumped_resources, Set.new )
94
+
95
+ # At this point we've done a scan of the dumped resources and mediatypes.
96
+ # In that scan we've discovered a bunch of types, however, many of those might have appeared in the JSON
97
+ # rendered in just shallow mode, so it is not guaranteed that we've seen all the available types.
98
+ # For that we'll do a (non-shallow) dump of all the types we found, and scan them until the scans do not
99
+ # yield types we haven't seen before
100
+ while !newfound.empty? do
101
+ dumped = newfound.collect(&:describe)
102
+ processed_types += newfound
103
+ newfound = scan_dump_for_types( dumped, processed_types )
104
+ end
105
+ processed_types
106
+ end
107
+
108
+ def scan_dump_for_types( data, processed_types )
109
+ newfound_types = Set.new
110
+ case data
111
+ when Array
112
+ data.collect{|item| newfound_types += scan_dump_for_types( item , processed_types ) }
113
+ when Hash
114
+ if data.key?(:type) && data[:type].kind_of?(Hash) && ( [:id,:name,:family] - data[:type].keys ).empty?
115
+ type_id = data[:type][:id]
116
+ unless type_id.nil? || type_id == Praxis::SimpleMediaType.id #SimpleTypes shouldn't be collected
117
+ unless types_by_id[type_id]
118
+ raise "Error! We have detected a reference to a 'Type' with id='#{type_id}' which is not derived from Attributor::Type" +
119
+ " Document generation cannot proceed."
120
+ end
121
+ newfound_types << types_by_id[type_id] unless processed_types.include? types_by_id[type_id]
122
+ end
123
+ end
124
+ data.values.map{|item| newfound_types += scan_dump_for_types( item , processed_types)}
125
+ end
126
+ newfound_types
127
+ end
128
+
44
129
  def write_version_file( version )
45
130
  # version_info = infos_by_version[version]
46
131
  # # Hack, let's "inherit/copy" all traits of a version from the global definition
@@ -159,7 +244,7 @@ module Praxis
159
244
  resources_by_version.keys.each do |version|
160
245
  FileUtils.mkdir_p @doc_root_dir + '/' + version
161
246
  end
162
- FileUtils.mkdir_p @doc_root_dir + '/unversioned'
247
+ FileUtils.mkdir_p @doc_root_dir + '/unversioned' if resources_by_version.keys.include?('n/a')
163
248
  end
164
249
 
165
250
  def normalize_media_types( mtis )
@@ -0,0 +1,273 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/inflector'
3
+
4
+ module Praxis
5
+ module EndpointDefinition
6
+ extend ActiveSupport::Concern
7
+
8
+ DEFAULT_RESOURCE_HREF_ACTION = :show
9
+
10
+ included do
11
+ @version = 'n/a'.freeze
12
+ @actions = Hash.new
13
+ @responses = Hash.new
14
+
15
+ @action_defaults = Trait.new &EndpointDefinition.generate_defaults_block
16
+
17
+ @version_options = {}
18
+ @metadata = {}
19
+ @traits = []
20
+
21
+ if self.name
22
+ @prefix = '/' + self.name.split("::").last.underscore
23
+ else
24
+ @prefix = '/'
25
+ end
26
+
27
+ @version_prefix = ''
28
+
29
+ @parent = nil
30
+ @parent_prefix = ''
31
+
32
+ @routing_prefix = nil
33
+
34
+ @on_finalize = Array.new
35
+
36
+ Application.instance.endpoint_definitions << self
37
+ end
38
+
39
+ def self.generate_defaults_block( version: nil )
40
+
41
+ # Ensure we inherit any base params defined in the API definition for the passed in version
42
+ base_attributes = if (base_params = ApiDefinition.instance.info(version).base_params)
43
+ base_params.attributes
44
+ else
45
+ {}
46
+ end
47
+
48
+ Proc.new do
49
+ unless base_attributes.empty?
50
+ params do
51
+ base_attributes.each do |base_name, base_attribute|
52
+ attribute base_name, base_attribute.type, **base_attribute.options
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def self.finalize!
60
+ Application.instance.endpoint_definitions.each do |resource_definition|
61
+ while (block = resource_definition.on_finalize.shift)
62
+ block.call
63
+ end
64
+ end
65
+ end
66
+
67
+
68
+
69
+ module ClassMethods
70
+ attr_reader :actions
71
+ attr_reader :responses
72
+ attr_reader :version_options
73
+ attr_reader :traits
74
+ attr_reader :version_prefix
75
+ attr_reader :parent_prefix
76
+
77
+ # opaque hash of user-defined medata, used to decorate the definition,
78
+ # and also available in the generated JSON documents
79
+ attr_reader :metadata
80
+
81
+ attr_accessor :controller
82
+
83
+ def display_name( string=nil )
84
+ unless string
85
+ return @display_name ||= self.name.split("::").last # Best guess at a display name?
86
+ end
87
+ @display_name = string
88
+ end
89
+
90
+ def on_finalize(&block)
91
+ if block_given?
92
+ @on_finalize << proc(&block)
93
+ end
94
+
95
+ @on_finalize
96
+ end
97
+
98
+ def prefix(prefix=nil)
99
+ return @prefix if prefix.nil?
100
+ @routing_prefix = nil # reset routing_prefix
101
+ @prefix = prefix
102
+ end
103
+
104
+ def media_type(media_type=nil)
105
+ return @media_type if media_type.nil?
106
+
107
+ if media_type.kind_of?(String)
108
+ media_type = SimpleMediaType.new(media_type)
109
+ end
110
+ @media_type = media_type
111
+ end
112
+
113
+
114
+ def parent(parent=nil, **mapping)
115
+ return @parent if parent.nil?
116
+
117
+ @routing_prefix = nil # reset routing_prefix
118
+
119
+ parent_action = parent.canonical_path
120
+ parent_route = parent_action.route.path
121
+
122
+ # if a mapping is passed, it *must* resolve any param name conflicts
123
+ unless mapping.any?
124
+ # assume last capture is the relevant one to replace
125
+ # if not... then I quit.
126
+ parent_param_name = parent_route.names.last
127
+
128
+ # more assumptions about names
129
+ parent_name = parent.name.demodulize.underscore.singularize
130
+
131
+ # put it together to find what we should call this new param
132
+ param = "#{parent_name}_#{parent_param_name}".to_sym
133
+ mapping[parent_param_name.to_sym] = param
134
+ end
135
+
136
+ # complete the mapping and massage the route
137
+ parent_route.names.collect(&:to_sym).each do |name|
138
+ if mapping.key?(name)
139
+ param = mapping[name]
140
+ # FIXME: this won't handle URI Template type paths, ie '/{parent_id}'
141
+ prefixed_path = parent_action.route.prefixed_path
142
+ @parent_prefix = prefixed_path.gsub(/(:)(#{name})(\W+|$)/, "\\1#{param.to_s}\\3")
143
+ else
144
+ mapping[name] = name
145
+ end
146
+ end
147
+
148
+ self.on_finalize do
149
+ self.inherit_params_from_parent(parent_action, **mapping)
150
+ end
151
+
152
+ @parent = parent
153
+ end
154
+
155
+ def inherit_params_from_parent(parent_action, **mapping)
156
+ actions.each do |name, action|
157
+ action.params do
158
+ mapping.each do |parent_name, name|
159
+ next if action.params && action.params.attributes.key?(name)
160
+
161
+ parent_attribute = parent_action.params.attributes[parent_name]
162
+
163
+ attribute name, parent_attribute.type, **parent_attribute.options
164
+ end
165
+ end
166
+ end
167
+
168
+ end
169
+
170
+ attr_writer :routing_prefix
171
+
172
+ def routing_prefix
173
+ return @routing_prefix if @routing_prefix
174
+
175
+ @routing_prefix = parent_prefix + prefix
176
+ end
177
+
178
+
179
+ def version(version=nil)
180
+ return @version unless version
181
+
182
+ @version = version
183
+ @action_defaults.instance_eval &EndpointDefinition.generate_defaults_block( version: version )
184
+ end
185
+
186
+
187
+ def canonical_path(action_name=nil)
188
+ if action_name
189
+ raise "Canonical path for #{self.name} is already defined as: '#{@canonical_action_name}'. 'canonical_path' can only be defined once." if @canonical_action_name
190
+ @canonical_action_name = action_name
191
+ else
192
+ # Resolution of the actual action definition needs to be done lazily, since we can use the `canonical_path` stanza
193
+ # at the top of the resource, well before the actual action is defined.
194
+ unless @canonical_action
195
+ href_action = @canonical_action_name || DEFAULT_RESOURCE_HREF_ACTION
196
+ @canonical_action = actions.fetch(href_action) do
197
+ raise "Error: trying to set canonical_href of #{self.name}. Action '#{href_action}' does not exist"
198
+ end
199
+ end
200
+ return @canonical_action
201
+ end
202
+ end
203
+
204
+ def to_href( params )
205
+ canonical_path.route.path.expand(params.transform_values(&:to_s))
206
+ end
207
+
208
+ def parse_href(path)
209
+ if path.kind_of?(::URI::Generic)
210
+ path = path.path
211
+ end
212
+ param_values = canonical_path.route.path.params(path)
213
+ attrs = canonical_path.params.attributes
214
+ param_values.each_with_object({}) do |(key,value),hash|
215
+ hash[key.to_sym] = attrs[key.to_sym].load(value,[key])
216
+ end
217
+ rescue => e
218
+ raise Praxis::Exception.new("Error parsing or coercing parameters from href: #{path}\n"+e.message)
219
+ end
220
+
221
+ def trait(trait_name)
222
+ unless ApiDefinition.instance.traits.has_key? trait_name
223
+ raise Exceptions::InvalidTrait.new("Trait #{trait_name} not found in the system")
224
+ end
225
+ trait = ApiDefinition.instance.traits.fetch(trait_name)
226
+ @traits << trait_name
227
+ end
228
+ alias_method :use, :trait
229
+
230
+ def action_defaults(&block)
231
+ if block_given?
232
+ @action_defaults.instance_eval(&block)
233
+ end
234
+
235
+ @action_defaults
236
+ end
237
+
238
+ def action(name, &block)
239
+ raise ArgumentError, "can not create ActionDefinition without block" unless block_given?
240
+ raise ArgumentError, "Action names must be defined using symbols (Got: #{name} (of type #{name.class}))" unless name.is_a? Symbol
241
+ @actions[name] = ActionDefinition.new(name, self, &block)
242
+ end
243
+
244
+ def description(text=nil)
245
+ @description = text if text
246
+ @description
247
+ end
248
+
249
+ def id
250
+ self.name.gsub('::'.freeze,'-'.freeze)
251
+ end
252
+
253
+ def describe(context: nil)
254
+ {}.tap do |hash|
255
+ hash[:description] = description
256
+ hash[:media_type] = media_type.describe(true) if media_type
257
+ hash[:actions] = actions.values.collect{|action| action.describe(context: context)}
258
+ hash[:name] = self.name
259
+ hash[:parent] = self.parent.id if self.parent
260
+ hash[:display_name] = self.display_name
261
+ hash[:metadata] = metadata
262
+ hash[:traits] = self.traits
263
+ end
264
+ end
265
+
266
+ def nodoc!
267
+ metadata[:doc_visibility] = :none
268
+ end
269
+
270
+ end
271
+
272
+ end
273
+ end