praxis 2.0.pre.9 → 2.0.pre.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +11 -0
  4. data/TODO.md +1 -4
  5. data/bin/praxis +11 -13
  6. data/lib/praxis.rb +10 -3
  7. data/lib/praxis/action_definition.rb +15 -13
  8. data/lib/praxis/action_definition/headers_dsl_compiler.rb +0 -7
  9. data/lib/praxis/api_general_info.rb +1 -1
  10. data/lib/praxis/application.rb +6 -2
  11. data/lib/praxis/blueprint.rb +357 -0
  12. data/lib/praxis/bootloader.rb +9 -3
  13. data/lib/praxis/bootloader_stages/environment.rb +15 -13
  14. data/lib/praxis/collection.rb +1 -11
  15. data/lib/praxis/config_hash.rb +44 -0
  16. data/lib/praxis/docs/{openapi → open_api}/info_object.rb +0 -0
  17. data/lib/praxis/docs/{openapi → open_api}/media_type_object.rb +0 -0
  18. data/lib/praxis/docs/{openapi → open_api}/operation_object.rb +0 -0
  19. data/lib/praxis/docs/{openapi → open_api}/parameter_object.rb +0 -0
  20. data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +0 -0
  21. data/lib/praxis/docs/{openapi → open_api}/request_body_object.rb +0 -0
  22. data/lib/praxis/docs/{openapi → open_api}/response_object.rb +0 -0
  23. data/lib/praxis/docs/{openapi → open_api}/responses_object.rb +0 -0
  24. data/lib/praxis/docs/{openapi → open_api}/schema_object.rb +0 -0
  25. data/lib/praxis/docs/{openapi → open_api}/server_object.rb +0 -0
  26. data/lib/praxis/docs/{openapi → open_api}/tag_object.rb +0 -0
  27. data/lib/praxis/docs/open_api_generator.rb +90 -5
  28. data/lib/praxis/endpoint_definition.rb +273 -0
  29. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +1 -1
  30. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +1 -1
  31. data/lib/praxis/extensions/field_expansion.rb +3 -36
  32. data/lib/praxis/extensions/pagination.rb +2 -2
  33. data/lib/praxis/extensions/pagination/ordering_params.rb +1 -1
  34. data/lib/praxis/extensions/pagination/pagination_params.rb +6 -4
  35. data/lib/praxis/field_expander.rb +90 -0
  36. data/lib/praxis/finalizable.rb +34 -0
  37. data/lib/praxis/mapper/selector_generator.rb +1 -1
  38. data/lib/praxis/media_type.rb +3 -68
  39. data/lib/praxis/plugin_concern.rb +1 -1
  40. data/lib/praxis/plugins/mapper_plugin.rb +7 -7
  41. data/lib/praxis/renderer.rb +88 -0
  42. data/lib/praxis/request.rb +1 -1
  43. data/lib/praxis/resource_definition.rb +2 -311
  44. data/lib/praxis/response_definition.rb +2 -10
  45. data/lib/praxis/response_template.rb +3 -3
  46. data/lib/praxis/router.rb +2 -2
  47. data/lib/praxis/routing_config.rb +1 -1
  48. data/lib/praxis/tasks/api_docs.rb +13 -63
  49. data/lib/praxis/tasks/routes.rb +1 -1
  50. data/lib/praxis/types/media_type_common.rb +1 -11
  51. data/lib/praxis/version.rb +1 -1
  52. data/praxis.gemspec +0 -1
  53. data/spec/functional_spec.rb +5 -9
  54. data/spec/praxis/action_definition_spec.rb +12 -20
  55. data/spec/praxis/blueprint_spec.rb +373 -0
  56. data/spec/praxis/bootloader_spec.rb +10 -2
  57. data/spec/praxis/collection_spec.rb +0 -13
  58. data/spec/praxis/config_hash_spec.rb +64 -0
  59. data/spec/praxis/{resource_definition_spec.rb → endpoint_definition_spec.rb} +37 -64
  60. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +4 -6
  61. data/spec/praxis/extensions/field_expansion_spec.rb +5 -24
  62. data/spec/praxis/field_expander_spec.rb +149 -0
  63. data/spec/praxis/media_type_identifier_spec.rb +5 -4
  64. data/spec/praxis/media_type_spec.rb +4 -93
  65. data/spec/praxis/renderer_spec.rb +188 -0
  66. data/spec/praxis/response_definition_spec.rb +0 -31
  67. data/spec/praxis/response_spec.rb +1 -1
  68. data/spec/praxis/router_spec.rb +8 -8
  69. data/spec/praxis/routing_config_spec.rb +3 -3
  70. data/spec/spec_app/app/controllers/instances.rb +13 -7
  71. data/spec/spec_app/design/media_types/instance.rb +1 -19
  72. data/spec/spec_app/design/media_types/volume.rb +1 -1
  73. data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -14
  74. data/spec/spec_app/design/resources/instances.rb +5 -8
  75. data/spec/spec_app/design/resources/volume_snapshots.rb +1 -1
  76. data/spec/spec_app/design/resources/volumes.rb +1 -1
  77. data/spec/support/spec_authorization_plugin.rb +1 -1
  78. data/spec/support/spec_blueprints.rb +72 -0
  79. data/spec/support/{spec_resource_definitions.rb → spec_endpoint_definitions.rb} +2 -2
  80. data/spec/support/spec_media_types.rb +6 -26
  81. data/tasks/thor/app.rb +8 -34
  82. data/tasks/thor/example.rb +45 -285
  83. data/tasks/thor/templates/generator/empty_app/.gitignore +0 -1
  84. data/tasks/thor/templates/generator/empty_app/Gemfile +7 -23
  85. data/tasks/thor/templates/generator/empty_app/README.md +1 -1
  86. data/tasks/thor/templates/generator/empty_app/Rakefile +4 -13
  87. data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.empty_directory +0 -0
  88. data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.gitkeep +0 -0
  89. data/tasks/thor/templates/generator/empty_app/config/environment.rb +25 -17
  90. data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.empty_directory +0 -0
  91. data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.gitkeep +0 -0
  92. data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.empty_directory +0 -0
  93. data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.gitkeep +0 -0
  94. data/tasks/thor/templates/generator/empty_app/docs/.empty_directory +0 -0
  95. data/tasks/thor/templates/generator/empty_app/docs/.gitkeep +0 -0
  96. data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +14 -9
  97. data/tasks/thor/templates/generator/example_app/.gitignore +1 -0
  98. data/tasks/thor/templates/generator/example_app/Gemfile +19 -0
  99. data/tasks/thor/templates/generator/example_app/Rakefile +54 -0
  100. data/tasks/thor/templates/generator/example_app/app/models/user.rb +6 -0
  101. data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +17 -0
  102. data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +46 -0
  103. data/tasks/thor/templates/generator/example_app/config.ru +31 -0
  104. data/tasks/thor/templates/generator/example_app/config/environment.rb +40 -0
  105. data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +11 -0
  106. data/tasks/thor/templates/generator/example_app/design/api.rb +18 -0
  107. data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +37 -0
  108. data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +26 -0
  109. data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +18 -0
  110. data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +42 -0
  111. data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +37 -0
  112. metadata +49 -135
  113. data/lib/api_browser/.bowerrc +0 -3
  114. data/lib/api_browser/.editorconfig +0 -21
  115. data/lib/api_browser/Gruntfile.js +0 -581
  116. data/lib/api_browser/app/index.html +0 -59
  117. data/lib/api_browser/app/js/app.js +0 -48
  118. data/lib/api_browser/app/js/controllers/action.js +0 -47
  119. data/lib/api_browser/app/js/controllers/controller.js +0 -10
  120. data/lib/api_browser/app/js/controllers/menu.js +0 -93
  121. data/lib/api_browser/app/js/controllers/trait.js +0 -10
  122. data/lib/api_browser/app/js/controllers/type.js +0 -24
  123. data/lib/api_browser/app/js/directives/attribute_description.js +0 -56
  124. data/lib/api_browser/app/js/directives/attribute_table.js +0 -28
  125. data/lib/api_browser/app/js/directives/conditional_requirements.js +0 -13
  126. data/lib/api_browser/app/js/directives/fixed_if_fits.js +0 -38
  127. data/lib/api_browser/app/js/directives/highlight.js +0 -14
  128. data/lib/api_browser/app/js/directives/menu_item.js +0 -59
  129. data/lib/api_browser/app/js/directives/no_container.js +0 -8
  130. data/lib/api_browser/app/js/directives/readable_list.js +0 -87
  131. data/lib/api_browser/app/js/directives/request_examples.js +0 -31
  132. data/lib/api_browser/app/js/directives/type_placeholder.js +0 -30
  133. data/lib/api_browser/app/js/directives/url.js +0 -15
  134. data/lib/api_browser/app/js/factories/Configuration.js +0 -12
  135. data/lib/api_browser/app/js/factories/Documentation.js +0 -61
  136. data/lib/api_browser/app/js/factories/Example.js +0 -51
  137. data/lib/api_browser/app/js/factories/PageInfo.js +0 -9
  138. data/lib/api_browser/app/js/factories/normalize_attributes.js +0 -20
  139. data/lib/api_browser/app/js/factories/prepare_template.js +0 -15
  140. data/lib/api_browser/app/js/factories/template_for.js +0 -128
  141. data/lib/api_browser/app/js/filters/attribute_name.js +0 -10
  142. data/lib/api_browser/app/js/filters/friendly_json.js +0 -5
  143. data/lib/api_browser/app/js/filters/has_requirement.js +0 -14
  144. data/lib/api_browser/app/js/filters/header_info.js +0 -9
  145. data/lib/api_browser/app/js/filters/is_empty.js +0 -8
  146. data/lib/api_browser/app/js/filters/markdown.js +0 -6
  147. data/lib/api_browser/app/js/filters/resource_name.js +0 -5
  148. data/lib/api_browser/app/js/filters/tag_requirement.js +0 -13
  149. data/lib/api_browser/app/sass/modules/_body.scss +0 -40
  150. data/lib/api_browser/app/sass/modules/_cloke.scss +0 -8
  151. data/lib/api_browser/app/sass/modules/_header.scss +0 -10
  152. data/lib/api_browser/app/sass/modules/_nav.scss +0 -7
  153. data/lib/api_browser/app/sass/modules/_sidebar.scss +0 -134
  154. data/lib/api_browser/app/sass/modules/_switch.scss +0 -55
  155. data/lib/api_browser/app/sass/modules/_table.scss +0 -13
  156. data/lib/api_browser/app/sass/praxis.scss +0 -70
  157. data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +0 -774
  158. data/lib/api_browser/app/views/action.html +0 -97
  159. data/lib/api_browser/app/views/builtin/field-selector.html +0 -24
  160. data/lib/api_browser/app/views/controller.html +0 -55
  161. data/lib/api_browser/app/views/directives/attribute_description.html +0 -2
  162. data/lib/api_browser/app/views/directives/attribute_description/default.html +0 -2
  163. data/lib/api_browser/app/views/directives/attribute_description/example.html +0 -13
  164. data/lib/api_browser/app/views/directives/attribute_description/headers.html +0 -8
  165. data/lib/api_browser/app/views/directives/attribute_description/member_options.html +0 -4
  166. data/lib/api_browser/app/views/directives/attribute_description/values.html +0 -14
  167. data/lib/api_browser/app/views/directives/attribute_table.html +0 -17
  168. data/lib/api_browser/app/views/directives/menu_item.html +0 -8
  169. data/lib/api_browser/app/views/directives/url.html +0 -3
  170. data/lib/api_browser/app/views/examples/general.html +0 -26
  171. data/lib/api_browser/app/views/home.html +0 -5
  172. data/lib/api_browser/app/views/layout.html +0 -8
  173. data/lib/api_browser/app/views/menu.html +0 -42
  174. data/lib/api_browser/app/views/navbar.html +0 -9
  175. data/lib/api_browser/app/views/trait.html +0 -13
  176. data/lib/api_browser/app/views/type.html +0 -6
  177. data/lib/api_browser/app/views/type/details.html +0 -33
  178. data/lib/api_browser/app/views/types/embedded/array.html +0 -2
  179. data/lib/api_browser/app/views/types/embedded/default.html +0 -12
  180. data/lib/api_browser/app/views/types/embedded/field-selector.html +0 -13
  181. data/lib/api_browser/app/views/types/embedded/links.html +0 -11
  182. data/lib/api_browser/app/views/types/embedded/requirements.html +0 -6
  183. data/lib/api_browser/app/views/types/embedded/single_req.html +0 -9
  184. data/lib/api_browser/app/views/types/embedded/struct.html +0 -14
  185. data/lib/api_browser/app/views/types/label/link.html +0 -1
  186. data/lib/api_browser/app/views/types/label/primitive.html +0 -1
  187. data/lib/api_browser/app/views/types/label/primitive_collection.html +0 -1
  188. data/lib/api_browser/app/views/types/label/type.html +0 -1
  189. data/lib/api_browser/app/views/types/label/type_collection.html +0 -1
  190. data/lib/api_browser/app/views/types/main/array.html +0 -22
  191. data/lib/api_browser/app/views/types/main/default.html +0 -23
  192. data/lib/api_browser/app/views/types/main/hash.html +0 -23
  193. data/lib/api_browser/app/views/types/standalone/array.html +0 -3
  194. data/lib/api_browser/app/views/types/standalone/default.html +0 -18
  195. data/lib/api_browser/app/views/types/standalone/struct.html +0 -2
  196. data/lib/api_browser/bower_template.json +0 -41
  197. data/lib/api_browser/package-lock.json +0 -7110
  198. data/lib/api_browser/package.json +0 -43
  199. data/lib/praxis/docs/generator.rb +0 -243
  200. data/lib/praxis/docs/link_builder.rb +0 -30
  201. data/lib/praxis/links.rb +0 -135
  202. data/lib/praxis/types/multipart.rb +0 -109
  203. data/spec/api_browser/directives/type_placeholder_spec.js +0 -134
  204. data/spec/api_browser/factories/configuration_spec.js +0 -32
  205. data/spec/api_browser/factories/documentation_spec.js +0 -100
  206. data/spec/api_browser/factories/normalize_attributes_spec.js +0 -92
  207. data/spec/api_browser/factories/template_for_spec.js +0 -67
  208. data/spec/api_browser/filters/attribute_name_spec.js +0 -23
  209. data/spec/praxis/types/multipart_spec.rb +0 -112
  210. data/tasks/thor/templates/generator/empty_app/.rspec +0 -1
  211. data/tasks/thor/templates/generator/empty_app/Guardfile +0 -3
  212. data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +0 -57
  213. data/tasks/thor/templates/generator/empty_app/docs/app.js +0 -1
  214. data/tasks/thor/templates/generator/empty_app/docs/styles.scss +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2a11ec74ee5a52178fe7aae10c503eb61407c739c40c262386e855e1abd40ee
4
- data.tar.gz: 71b397991329ea9726b366938de75768bedf812324e4b2d768ac7dae3f25e33e
3
+ metadata.gz: dd76d8bd13954d5607d009ee37ff7fc1a8ef8a2c6c8c87544dddaee332d984bf
4
+ data.tar.gz: 719bffed7ea98fe42d7c620cd4377c4201c9d23e49a9777e981647d671b39ffd
5
5
  SHA512:
6
- metadata.gz: 21fbd6dbb60486cea3ec5cdd18a1b4178873ff6343182f2b44a11d71cbdac1664bec005760b77511de342fc92a63abe7864c85f10ffc1c3953734a1069f2520d
7
- data.tar.gz: 7c0f8a5c417ad9ecd76fd67e08f1f219cc274c3bbd30ec88d7ae669e0a10d4ec5585c365ad7c3a38163b3d4f367897e7129bbbda66664947e82c37dc90213f08
6
+ metadata.gz: 18e87e4563faa7a1ffbba174634f1ebabe57320b8324e4ac23a711f5785906482412db711c83308ff87264d48cc496f8bd118b5a5ac77f1d6bd8f09e543f90ed
7
+ data.tar.gz: 9da869a6acecf7c04273ef7dc0519be0cf8b3d00c29df16c851df04f5ea52d7cf4d62123e25533b8887c0a30b940155abfbd812f4fe2b347d1139df91e3e124f
data/.gitignore CHANGED
@@ -14,3 +14,5 @@ lib/api_browser/node_modules/
14
14
  lib/api_browser/app/bower_components/
15
15
  lib/api_browser/.tmp/
16
16
  lib/api_browser/bower.json
17
+
18
+ development.sqlite3
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## next
4
4
 
5
+ - Simple, but pervasive breaking change: Rename `ResourceDefinition` to `EndpointDefinition` (but same functionality).
6
+ - Remove all deprecated features (and raise error describing it's not supported yet)
7
+ - Remove `Links` and `LinkBuilder`. Those seem unnecessary from a Framework point of view as they aren't clear most
8
+ applications would benefit from it. Applications can choose to add that functionality on their own if so desire.
9
+ - Rebuilt app generators: for new empty app, and example app.
10
+ - Updated default layout to match new naming structure and more concepts commonly necessary for normal applications.
11
+ - Completely removed the native Praxis API documentation browser in lieu of OpenAPI 3.x standards, and reDoc.
12
+ - Remove dependency from praxis-blueprints, as simplified subset of its code has now been included in this repo:
13
+ - no more views for mediatypes. A default fieldset will be automatically defined which will be the default set of attributes to render with. This default fieldset will only contain simple direct attributes (i.e., non blueprint/mediatype attributes). One can override the default by explicitly defining one using the `default_fieldset` DSL, similar to how views were defined before.
14
+ - Folded the pagination/ordering extensions to activate within the `build_query` method of the mapper plugin extension. This way all the field selection, filtering and pagination/ordering will kick in automatically when that plugin is included.
15
+
5
16
  ## 2.0.pre.9
6
17
 
7
18
  - Refined OpenAPI doc generation to output only non-null attributes in the InfoObject.
data/TODO.md CHANGED
@@ -3,17 +3,14 @@
3
3
  ## Things to delete
4
4
 
5
5
  * views (just use a sensible default view of just simple types?)
6
- * make handlers hang from a singleton (and possibly get rid of xml encoding as well)
7
- * remove app instances?
8
6
  * remove collection summary things...
9
7
  * FieldResolver?? and that conditional dependency thing...?
10
8
  * simplify or change examples? ...maybe get rid of randexp.
11
- * get rid of doc browser in lieu to OAPI and redoc
12
- * traits? ... we still use them...
13
9
  * NOTE: make sure the types we use only expose the json-schema types...no longer things like Hash/Struct/Symbol ...(that might be good only for coding/implementation, not for documentation)
14
10
  * Plugins? ... maybe leave them out for the moment?
15
11
  * change errors to be machine readable
16
12
  * change naming of resource definition to endpoint definition
13
+ * get rid of deprecations
17
14
 
18
15
 
19
16
  ## DONE
data/bin/praxis CHANGED
@@ -13,7 +13,8 @@ if ["routes","docs","console"].include? ARGV[0]
13
13
  require 'rake'
14
14
  require 'praxis'
15
15
  require 'praxis/tasks'
16
-
16
+ load 'Rakefile' # Ensure that we read the App's Rakefile, to pickup any definitions etc.
17
+
17
18
  case ARGV[0]
18
19
  when "routes"
19
20
  Rake::Task['praxis:routes'].invoke(ARGV[1])
@@ -24,9 +25,8 @@ if ["routes","docs","console"].include? ARGV[0]
24
25
  when 'generate'
25
26
  'praxis:docs:generate'
26
27
  when 'package'
27
- 'praxis:docs:build'
28
+ 'praxis:docs:package'
28
29
  end
29
- # task_name = ARGV[1] == 'browser' ? 'praxis:doc_browser' : 'praxis:api_docs'
30
30
  Rake::Task[task_name].invoke
31
31
  when "console"
32
32
  Rake::Task['praxis:console'].invoke
@@ -71,19 +71,17 @@ class PraxisGenerator < Thor
71
71
 
72
72
  desc_for "new APP_NAME", ::PraxisGen::App, :new
73
73
  def new(app_name)
74
- ::PraxisGen::App.start(['new' , app_name])
74
+ gen = ::PraxisGen::App.new([app_name])
75
+ gen.destination_root = app_name
76
+ gen.invoke_all
75
77
  end
76
78
 
77
- desc_for "example APP_NAME", ::PraxisGen::Example, :new
79
+ desc_for "example APP_NAME", ::PraxisGen::Example, :example
78
80
  def example(app_name)
79
- ::PraxisGen::Example.start(['new', app_name])
80
- end
81
-
82
- desc_for "generate APP_NAME", ::PraxisGen::Example, :new, "DEPRECATED!: "
83
- def generate(app_name)
84
- warn "This is a deprecated method.\nTo generate a hello world example, please use:\n praxis example #{app_name} "
85
- end
86
-
81
+ gen = ::PraxisGen::Example.new([app_name])
82
+ gen.destination_root = app_name
83
+ gen.invoke(:example)
84
+ end
87
85
  end
88
86
 
89
87
  PraxisGenerator.start(ARGV)
@@ -1,6 +1,5 @@
1
1
  require 'rack'
2
2
  require 'attributor'
3
- require 'praxis-blueprints'
4
3
 
5
4
  require 'active_support/concern'
6
5
  require 'praxis/request_superclassing'
@@ -33,7 +32,8 @@ module Praxis
33
32
  autoload :Plugin, 'praxis/plugin'
34
33
  autoload :PluginConcern, 'praxis/plugin_concern'
35
34
  autoload :Request, 'praxis/request'
36
- autoload :ResourceDefinition, 'praxis/resource_definition'
35
+ autoload :ResourceDefinition, 'praxis/resource_definition' # Deprecated: this is to support an easier transition
36
+ autoload :EndpointDefinition, 'praxis/endpoint_definition'
37
37
  autoload :Response, 'praxis/response'
38
38
  autoload :ResponseDefinition, 'praxis/response_definition'
39
39
  autoload :ResponseTemplate, 'praxis/response_template'
@@ -43,6 +43,14 @@ module Praxis
43
43
  autoload :SimpleMediaType, 'praxis/simple_media_type'
44
44
  autoload :Stage, 'praxis/stage'
45
45
  autoload :Trait, 'praxis/trait'
46
+ autoload :ConfigHash, 'praxis/config_hash'
47
+ autoload :Finalizable, 'praxis/finalizable'
48
+
49
+ # Sort of part of the old Blueprints gem...but they're really not scoped...
50
+ autoload :Blueprint, 'praxis/blueprint'
51
+ autoload :FieldExpander, 'praxis/field_expander'
52
+ autoload :Renderer, 'praxis/renderer'
53
+
46
54
 
47
55
  autoload :Notifications, 'praxis/notifications'
48
56
  autoload :MiddlewareApp, 'praxis/middleware_app'
@@ -62,7 +70,6 @@ module Praxis
62
70
 
63
71
  autoload :MediaType, 'praxis/media_type'
64
72
  autoload :MediaTypeIdentifier, 'praxis/media_type_identifier'
65
- autoload :Multipart, 'praxis/types/multipart'
66
73
  autoload :Collection, 'praxis/collection'
67
74
 
68
75
  autoload :MultipartParser, 'praxis/multipart/parser'
@@ -11,7 +11,7 @@ module Praxis
11
11
  class ActionDefinition
12
12
 
13
13
  attr_reader :name
14
- attr_reader :resource_definition
14
+ attr_reader :endpoint_definition
15
15
  attr_reader :api_definition
16
16
  attr_reader :route
17
17
  attr_reader :responses
@@ -31,33 +31,33 @@ module Praxis
31
31
  self.doc_decorations << callback
32
32
  end
33
33
 
34
- def initialize(name, resource_definition, **opts, &block)
34
+ def initialize(name, endpoint_definition, **opts, &block)
35
35
  @name = name
36
- @resource_definition = resource_definition
36
+ @endpoint_definition = endpoint_definition
37
37
  @responses = Hash.new
38
38
  @metadata = Hash.new
39
39
  @route = nil
40
40
  @traits = []
41
41
 
42
- if (media_type = resource_definition.media_type)
42
+ if (media_type = endpoint_definition.media_type)
43
43
  if media_type.kind_of?(Class) && media_type < Praxis::Types::MediaTypeCommon
44
44
  @reference_media_type = media_type
45
45
  end
46
46
  end
47
47
 
48
- version = resource_definition.version
49
- api_info = ApiDefinition.instance.info(resource_definition.version)
48
+ version = endpoint_definition.version
49
+ api_info = ApiDefinition.instance.info(endpoint_definition.version)
50
50
 
51
- route_base = "#{api_info.base_path}#{resource_definition.version_prefix}"
52
- prefix = Array(resource_definition.routing_prefix)
51
+ route_base = "#{api_info.base_path}#{endpoint_definition.version_prefix}"
52
+ prefix = Array(endpoint_definition.routing_prefix)
53
53
 
54
54
  @routing_config = RoutingConfig.new(version: version, base: route_base, prefix: prefix)
55
55
 
56
- resource_definition.traits.each do |trait|
56
+ endpoint_definition.traits.each do |trait|
57
57
  self.trait(trait)
58
58
  end
59
59
 
60
- resource_definition.action_defaults.apply!(self)
60
+ endpoint_definition.action_defaults.apply!(self)
61
61
 
62
62
  self.instance_eval(&block) if block_given?
63
63
  end
@@ -71,7 +71,6 @@ module Praxis
71
71
  trait.apply!(self)
72
72
  traits << trait_name
73
73
  end
74
- alias_method :use, :trait
75
74
 
76
75
  def update_attribute(attribute, options, block)
77
76
  attribute.options.merge!(options)
@@ -246,7 +245,7 @@ module Praxis
246
245
  def params_description(example:)
247
246
  route_params = []
248
247
  if route.nil?
249
- warn "Warning: No route defined for #{resource_definition.name}##{name}."
248
+ warn "Warning: No route defined for #{endpoint_definition.name}##{name}."
250
249
  else
251
250
  route_params = route.path.
252
251
  named_captures.
@@ -343,6 +342,9 @@ module Praxis
343
342
  metadata[:doc_visibility] = :none
344
343
  end
345
344
 
346
-
345
+ # [DEPRECATED] - Warn of the change of method name for the transition
346
+ def resource_definition
347
+ raise "Praxis::ActionDefinition does not use `resource_definition` any longer. Use `endpoint_definition` instead."
348
+ end
347
349
  end
348
350
  end
@@ -27,13 +27,6 @@ module Praxis
27
27
  end
28
28
  key name , String, **options
29
29
  end
30
-
31
- # Override the attribute to really call "key" in the hash (for temporary backwards compat)
32
- def attribute(name, attr_type=nil, **opts, &block)
33
- warn "[DEPRECATION] `attribute` is deprecated when defining headers. Please use `key` instead."
34
- key(name, attr_type, **opts, &block)
35
- end
36
-
37
30
  end
38
31
  end
39
32
  end
@@ -122,7 +122,7 @@ module Praxis
122
122
  global_path = @global_info.base_path
123
123
  if version_with == :path
124
124
  global_pattern = Mustermann.new(global_path)
125
- global_path = global_pattern.expand(Request::API_VERSION_PARAM_NAME => self.version)
125
+ global_path = global_pattern.expand(Request::API_VERSION_PARAM_NAME => self.version.to_s)
126
126
  end
127
127
 
128
128
  version_path = @data.fetch(:base_path,'')
@@ -8,7 +8,7 @@ module Praxis
8
8
 
9
9
  attr_reader :router
10
10
  attr_reader :controllers
11
- attr_reader :resource_definitions
11
+ attr_reader :endpoint_definitions
12
12
  attr_reader :app
13
13
  attr_reader :builder
14
14
 
@@ -32,7 +32,7 @@ module Praxis
32
32
 
33
33
  def initialize
34
34
  @controllers = Set.new
35
- @resource_definitions = Set.new
35
+ @endpoint_definitions = Set.new
36
36
 
37
37
  @error_handler = ErrorHandler.new
38
38
  @validation_handler = ValidationHandler.new
@@ -123,5 +123,9 @@ module Praxis
123
123
  @config.set(config)
124
124
  end
125
125
 
126
+ # [DEPRECATED] - Warn of the change of method name for the transition
127
+ def resource_definitions
128
+ raise "Praxis::Application.instance does not use `resource_definitions` any longer. Use `endpoint_definitions` instead."
129
+ end
126
130
  end
127
131
  end
@@ -0,0 +1,357 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Praxis
4
+ class Blueprint
5
+
6
+ # Simple helper class that can parse the `attribute :foobar` dsl into
7
+ # an equivalent structure hash. Example:
8
+ # do
9
+ # attribute :one
10
+ # attribute :complex do
11
+ # attribute :sub1
12
+ # end
13
+ # end
14
+ # is parsed as: { one: true, complex: { sub1: true} }
15
+ class FieldsetParser
16
+ def initialize( &block)
17
+ @hash = nil
18
+ @block = block
19
+ end
20
+
21
+ def attribute(name, **args, &block)
22
+ raise "Default fieldset definitions do not accept parameters (got: #{args})" \
23
+ "If you're upgrading from a previous version of Praxis and still using the view :default " \
24
+ "block syntax, make sure you don't use any view: X parameters when you define the attributes " \
25
+ "(expand them explicitly if you want deeper structure)" \
26
+ "The offending view with parameters is defined in:\n#{Kernel.caller.first}" unless args.empty?
27
+ @hash[name] = block_given? ? FieldsetParser.new(&block).fieldset : true
28
+ end
29
+
30
+ def fieldset
31
+ return @hash if @hash
32
+ # Lazy eval
33
+ @hash = {}
34
+ instance_eval(&@block)
35
+ @hash
36
+ end
37
+ end
38
+ include Attributor::Type
39
+ include Attributor::Dumpable
40
+
41
+ extend Finalizable
42
+
43
+ @@caching_enabled = false
44
+
45
+ attr_reader :validating
46
+ attr_accessor :object
47
+
48
+ class << self
49
+ attr_reader :attribute
50
+ attr_reader :options
51
+ attr_accessor :reference
52
+ end
53
+
54
+ def self.inherited(klass)
55
+ super
56
+
57
+ klass.instance_eval do
58
+ @options = {}
59
+ @domain_model = Object
60
+ @default_fieldset = {}
61
+ end
62
+ end
63
+
64
+ # Override default new behavior to support memoized creation through an IdentityMap
65
+ def self.new(object)
66
+ # TODO: do we want to allow the identity map thing in the object?...maybe not.
67
+ if @@caching_enabled
68
+ return self.cache[object] ||= begin
69
+ blueprint = self.allocate
70
+ blueprint.send(:initialize, object)
71
+ blueprint
72
+ end
73
+ end
74
+
75
+ blueprint = self.allocate
76
+ blueprint.send(:initialize, object)
77
+ blueprint
78
+ end
79
+
80
+ def self.family
81
+ 'hash'
82
+ end
83
+
84
+ def self.attributes(opts = {}, &block)
85
+ if block_given?
86
+ raise 'Redefining Blueprint attributes is not currently supported' if self.const_defined?(:Struct, false)
87
+
88
+ if opts.key?(:reference) && opts[:reference] != self.reference
89
+ raise "Reference mismatch in #{self.inspect}. Given :reference option #{opts[:reference].inspect}, while using #{self.reference.inspect}"
90
+ elsif self.reference
91
+ opts[:reference] = self.reference # pass the reference Class down
92
+ else
93
+ opts[:reference] = self
94
+ end
95
+
96
+ @options.merge!(opts)
97
+ @block = block
98
+
99
+ return @attribute
100
+ end
101
+
102
+ raise "@attribute not defined yet for #{self.name}" unless @attribute
103
+
104
+ @attribute.attributes
105
+ end
106
+
107
+ def self.domain_model(klass = nil)
108
+ return @domain_model if klass.nil?
109
+ @domain_model = klass
110
+ end
111
+
112
+ def self.check_option!(name, value)
113
+ Attributor::Struct.check_option!(name, value)
114
+ end
115
+
116
+ def self.load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, **options)
117
+ case value
118
+ when self
119
+ value
120
+ when nil, Hash, String
121
+ if (value = self.attribute.load(value, context, **options))
122
+ self.new(value)
123
+ end
124
+ else
125
+ if value.is_a?(self.domain_model) || value.is_a?(self::Struct)
126
+ # Wrap the value directly
127
+ self.new(value)
128
+ else
129
+ # Wrap the object inside the domain_model
130
+ self.new(domain_model.new(value))
131
+ end
132
+ end
133
+ end
134
+
135
+ class << self
136
+ alias from load
137
+ end
138
+
139
+ def self.caching_enabled?
140
+ @@caching_enabled
141
+ end
142
+
143
+ def self.caching_enabled=(caching_enabled)
144
+ @@caching_enabled = caching_enabled
145
+ end
146
+
147
+ # Fetch current blueprint cache, scoped by this class
148
+ def self.cache
149
+ Thread.current[:praxis_blueprints_cache][self]
150
+ end
151
+
152
+ def self.cache=(cache)
153
+ Thread.current[:praxis_blueprints_cache] = cache
154
+ end
155
+
156
+ def self.valid_type?(value)
157
+ value.is_a?(self) || value.is_a?(self.attribute.type)
158
+ end
159
+
160
+ def self.example(context = nil, **values)
161
+ context = case context
162
+ when nil
163
+ ["#{self.name}-#{values.object_id}"]
164
+ when ::String
165
+ [context]
166
+ else
167
+ context
168
+ end
169
+
170
+ self.new(self.attribute.example(context, values: values))
171
+ end
172
+
173
+ def self.validate(value, context = Attributor::DEFAULT_ROOT_CONTEXT, _attribute = nil)
174
+ raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context.nil?
175
+ context = [context] if context.is_a? ::String
176
+
177
+ unless value.is_a?(self)
178
+ raise ArgumentError, "Error validating #{Attributor.humanize_context(context)} as #{self.name} for an object of type #{value.class.name}."
179
+ end
180
+
181
+ value.validate(context)
182
+ end
183
+
184
+ def self.default_fieldset(&block)
185
+ return @default_fieldset unless block_given?
186
+
187
+ @block_for_default_fieldset = block
188
+ end
189
+
190
+ def self.view(name, **options, &block)
191
+ unless name == :default
192
+ raise "[ERROR] Views are no longer supported. Please use fully expanded fields when rendering.\n" \
193
+ "NOTE that defining the :default view is deprecated, but still temporarily allowed, as an alias to define the default_fieldset.\n" \
194
+ "A view for name #{name} is attempted to be defined in:\n#{Kernel.caller.first}"
195
+ end
196
+ raise "Cannot define the default fieldset through the default view unless a block is passed" unless block_given?
197
+ puts "[DEPRECATED] default fieldsets should be defined through `default_fieldset` instead of using the view :default block.\n" \
198
+ "A default view is attempted to be defined in:\n#{Kernel.caller.first}"
199
+ default_fieldset(&block)
200
+ end
201
+
202
+ def self.parse_default_fieldset(block)
203
+ @default_fieldset = FieldsetParser.new(&block).fieldset
204
+ @block_for_default_fieldset = nil
205
+ end
206
+
207
+ # renders using the implicit default fieldset
208
+ def self.dump(object, context: Attributor::DEFAULT_ROOT_CONTEXT, **opts)
209
+ object = self.load(object, context, **opts)
210
+ return nil if object.nil?
211
+
212
+ object.render(context: context, **opts)
213
+ end
214
+
215
+ class << self
216
+ alias render dump
217
+ end
218
+
219
+ # Internal finalize! logic
220
+ def self._finalize!
221
+ if @block
222
+ self.define_attribute!
223
+ self.define_readers!
224
+ # Don't blindly override a the default fieldset if the MediaType wants to define it on its own
225
+ if @block_for_default_fieldset
226
+ parse_default_fieldset(@block_for_default_fieldset)
227
+ else
228
+ self.generate_default_fieldset!
229
+ end
230
+ self.resolve_domain_model!
231
+ end
232
+ super
233
+ end
234
+
235
+ def self.resolve_domain_model!
236
+ return unless self.domain_model.is_a?(String)
237
+
238
+ @domain_model = self.domain_model.constantize
239
+ end
240
+
241
+ def self.define_attribute!
242
+ @attribute = Attributor::Attribute.new(Attributor::Struct, @options, &@block)
243
+ @block = nil
244
+ @attribute.type.anonymous_type true
245
+ self.const_set(:Struct, @attribute.type)
246
+ end
247
+
248
+ def self.define_readers!
249
+ self.attributes.each do |name, _attribute|
250
+ name = name.to_sym
251
+
252
+ # Don't redefine existing methods
253
+ next if self.instance_methods.include? name
254
+
255
+ define_reader! name
256
+ end
257
+ end
258
+
259
+ def self.define_reader!(name)
260
+ attribute = self.attributes[name]
261
+ # TODO: profile and optimize
262
+ # because we use the attribute in the reader,
263
+ # it's likely faster to use define_method here
264
+ # than module_eval, but we should make sure.
265
+ define_method(name) do
266
+ value = @object.__send__(name)
267
+ return value if value.nil? || value.is_a?(attribute.type)
268
+ attribute.load(value)
269
+ end
270
+ end
271
+
272
+ def self.generate_default_fieldset!
273
+ attributes = self.attributes
274
+
275
+ @default_fieldset = {}
276
+ attributes.each do |name, attr|
277
+ the_type = (attr.type < Attributor::Collection) ? attr.type.member_type : attr.type
278
+ next if the_type < Blueprint
279
+ # Note: we won't try to expand fields here, as we want to be lazy (and we're expanding)
280
+ # every time a request comes in anyway. This could be an optimization we do at some point
281
+ # or we can 'memoize it' to avoid trying to expand it over an over...
282
+ @default_fieldset[name] = true
283
+ end
284
+ end
285
+
286
+ def initialize(object)
287
+ @object = object
288
+ @validating = false
289
+ end
290
+
291
+ # By default we'll use the object identity, to avoid rendering the same object twice
292
+ # Override, if there is a better way cache things up
293
+ def _cache_key
294
+ self.object
295
+ end
296
+
297
+ # Render the wrapped data with the given fields (or using the default fieldset otherwise)
298
+ def render(fields: self.class.default_fieldset, context: Attributor::DEFAULT_ROOT_CONTEXT, renderer: Renderer.new, **opts)
299
+
300
+ # Accept a simple array of fields, and transform it to a 1-level hash with true values
301
+ if fields.is_a? Array
302
+ fields = fields.each_with_object({}) { |field, hash| hash[field] = true }
303
+ end
304
+
305
+ expanded = Praxis::FieldExpander.new.expand(self, fields)
306
+ renderer.render(self, fields, context: context)
307
+ end
308
+
309
+ alias dump render
310
+
311
+ def to_h
312
+ Attributor.recursive_to_h(@object)
313
+ end
314
+
315
+ def validate(context = Attributor::DEFAULT_ROOT_CONTEXT)
316
+ raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context.nil?
317
+ context = [context] if context.is_a? ::String
318
+ keys_with_values = []
319
+
320
+ raise 'validation conflict' if @validating
321
+ @validating = true
322
+
323
+ errors = []
324
+ self.class.attributes.each do |sub_attribute_name, sub_attribute|
325
+ sub_context = self.class.generate_subcontext(context, sub_attribute_name)
326
+ value = self.send(sub_attribute_name)
327
+ keys_with_values << sub_attribute_name unless value.nil?
328
+
329
+ if value.respond_to?(:validating) # really, it's a thing with sub-attributes
330
+ next if value.validating
331
+ end
332
+ errors.concat(sub_attribute.validate(value, sub_context))
333
+ end
334
+ self.class.attribute.type.requirements.each do |req|
335
+ validation_errors = req.validate(keys_with_values, context)
336
+ errors.concat(validation_errors) unless validation_errors.empty?
337
+ end
338
+ errors
339
+ ensure
340
+ @validating = false
341
+ end
342
+
343
+ # generic semi-private getter used by Renderer
344
+ def _get_attr(name)
345
+ self.send(name)
346
+ end
347
+
348
+ # Delegates the json-schema methods to the underlying attribute/member_type
349
+ def self.as_json_schema(**args)
350
+ @attribute.type.as_json_schema(args)
351
+ end
352
+
353
+ def self.json_schema_type
354
+ @attribute.type.json_schema_type
355
+ end
356
+ end
357
+ end