praxis 2.0.pre.7 → 2.0.pre.12

Sign up to get free protection for your applications and to get access to all the features.
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 +29 -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 +2 -2
  22. data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +12 -15
  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 +46 -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 +5 -1
  39. data/lib/praxis/extensions/pagination/pagination_params.rb +10 -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 +175 -8
  72. data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +131 -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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1854f289afef574a8f46cc2e98ab1426a39cf908f1a761b3b294b044b3ea1093
4
- data.tar.gz: 70dfe074269ea807771f891958096b66ea4d2a2a5fa399722527a7114867ac79
3
+ metadata.gz: cf4b54b686373509e0920a93d482e29f4c198491f88d382b6ff429e5128d6574
4
+ data.tar.gz: '08b2d7342069e8584676e03c828e144b99eb9282b193113b987d8060cb143cfd'
5
5
  SHA512:
6
- metadata.gz: 6d135d076923a6564b1c7b956a9ad069951d9033b6450eb135772dc7f03a9e1cf36edc0e650a9970694dcb707164326d646c638f917bd5bd55cff51f3a5b0c0c
7
- data.tar.gz: e6c6f36d894f2ff67d85d56073bc54f68d4450a757870c04aecae7d8f33daddd1bdadf2142e36407d12be85e718675624d1229776759277f196f462e574f14b7
6
+ metadata.gz: ef7cd7fe985d4af76cf7ec6459e58990b8dbc6f3c4fac5c26ce04d56e23a3d743a0f4bb776f4d5c913224e031567e9993ad52e611e4258ee853cdf86b3099255
7
+ data.tar.gz: f4fe45f97c9bc26aa513a24ee358b04dc114af0e7f4849071f699c0c22df460253452dd54e9bcb510fc4f977aa9104774f70c882f46b8aa937404e79814d6f3d
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
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.3
1
+ 2.7.1
data/.travis.yml CHANGED
@@ -1,10 +1,8 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.4
5
- - 2.5
6
4
  - 2.6
7
- - 2.7
5
+ - 2.7
8
6
  script:
9
7
  - bundle exec rspec spec
10
8
  branches:
data/CHANGELOG.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  ## next
4
4
 
5
+ ## 2.0.pre.12
6
+
7
+ * Rebuilt API filters to support a much richer syntax. One can now use ANDs and ORs (with ANDs having order precedence), as well as group them with parenthesis. The same individual filter operands are supported. For example: 'email=*@gmail.com&(friends.first_name=Joe*,Patty|friends.last_name=Smith)
8
+
9
+ ## 2.0.pre.11
10
+
11
+ - Remove MapperPlugin's `set_selectors` (made `selector_generator` lazy instead), and ensure it includes the rendering extensions to the Controllers. Less things to configure if you opt into the Mapper way.
12
+ - Built scaffolding generator for quickly creating a new API endpoint in the praxis binary (it builds endpoint+mediatype+controller+resource at one, with useful base code and comments)
13
+ - Dropped support for Ruby 2.4 and 2.5 as some of the newest dependent gems are dropping it as well.
14
+ - Simplify filters_mapping definition, by not requiring to define same-name mappings if the underlying model has an attribute with the same exact name. i.e., a `name: :name` entry is not necessary if the model has a `:name` attribute.
15
+
16
+ ## 2.0.pre.10
17
+
18
+ - Simple, but pervasive breaking change: Rename `ResourceDefinition` to `EndpointDefinition` (but same functionality).
19
+ - Remove all deprecated features (and raise error describing it's not supported yet)
20
+ - Remove `Links` and `LinkBuilder`. Those seem unnecessary from a Framework point of view as they aren't clear most
21
+ applications would benefit from it. Applications can choose to add that functionality on their own if so desire.
22
+ - Rebuilt app generators: for new empty app, and example app.
23
+ - Updated default layout to match new naming structure and more concepts commonly necessary for normal applications.
24
+ - Completely removed the native Praxis API documentation browser in lieu of OpenAPI 3.x standards, and reDoc.
25
+ - Remove dependency from praxis-blueprints, as simplified subset of its code has now been included in this repo:
26
+ - 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.
27
+ - 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.
28
+
29
+ ## 2.0.pre.9
30
+
31
+ - Refined OpenAPI doc generation to output only non-null attributes in the InfoObject.
32
+ - Fixed filtering params validation to properly allow null values for the "!" and "!!" operators
33
+
5
34
  ## 2.0.pre.6
6
35
 
7
36
  - Removed the explicit `links` helpers from a `MediaType`. There was too much magic and assumptions built into it. Things can still be built in a custom basis (or through a plugin) if necessary.
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
@@ -46,7 +46,7 @@ class PraxisGenerator < Thor
46
46
  def routes
47
47
  end
48
48
 
49
- desc "docs [generate|browser|package]", <<-EOF
49
+ desc "docs [generate|browser|package]", <<~EOF
50
50
  Generates API documentation and a Web App to inspect it
51
51
  generate - Generates the JSON docs
52
52
  browser - (default) Generates JSON docs, and automatically starts a Web app to browse them.
@@ -71,19 +71,74 @@ 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])
81
+ gen = ::PraxisGen::Example.new([app_name])
82
+ gen.destination_root = app_name
83
+ gen.invoke(:example)
80
84
  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
+
86
+ desc_for "g COLLECTION_NAME", ::PraxisGen::Scaffold, :g
87
+ # Cannot use the argument below or it will apply to all commands (the action in the class has it)
88
+ # argument :collection_name, required: false
89
+ # The options, however, since they're optional are fine (But need to be duplicated from the class :( )
90
+ option :version, required: false, default: '1',
91
+ desc: 'Version string for the API endpoint. This also dictates the directory structure (i.e., v1/endpoints/...))'
92
+ option :design, type: :boolean, default: true,
93
+ desc: 'Include the Endpoint and MediaType files for the collection'
94
+ option :implementation, type: :boolean, default: true,
95
+ desc: 'Include the Controller and (possibly the) Resource files for the collection (see --no-resource)'
96
+ option :resource, type: :boolean, default: true,
97
+ desc: 'Disable (or enable) the creation of the Resource files when generating implementation'
98
+ option :model, type: :string, enum: ['activerecord','sequel'],
99
+ desc: 'It also generates a model for the given ORM. An empty --model flag will default to activerecord'
100
+ option :actions, type: :string, default: 'crud', enum: ['cr','cru','crud','u','ud','d'],
101
+ desc: 'Specifies the actions to generate for the API. cr=create, u=update, d=delete. Index and show actions are always generated'
102
+ def g(*args)
103
+ # Because we cannot share the :collection_name argument, we need to do this check here, before
104
+ # we "parse" it and pass it to the g command
105
+ unless args.size == 1
106
+ ::PraxisGen::Scaffold.command_help(shell,:g)
107
+ exit 1
108
+ end
109
+
110
+ collection_name,_ = args
111
+ ::PraxisGen::Scaffold.new([collection_name],options).invoke(:g)
112
+ if options[:model]
113
+ # Make it easy to be able to both enable or not enable the creation of the model, by passing --model=...
114
+ # but also make it easy so that if there is no value for it, it default to activerecord
115
+ opts = {orm: options[:model] }
116
+ opts[:orm] = 'activerecord' if opts[:orm] == 'model' # value is model param passed by no value
117
+ ::PraxisGen::Model.new([collection_name.singularize],opts).invoke(:g)
118
+ end
85
119
  end
86
-
120
+
121
+ # Initially, the idea was to build some quick model generator, but I think it's better to keep it
122
+ # simple and just use the scaffold generator with `--no-implementation --no-design --model` instead
123
+ # Left here in case we want to rescue it
124
+ # desc_for "gmodel MODEL_NAME", ::PraxisGen::Model, :g
125
+ # # Cannot use the argument below or it will apply to all commands (the action in the class has it)
126
+ # # argument :collection_name, required: false
127
+ # # The options, however, since they're optional are fine (But need to be duplicated from the class :( )
128
+ # option :orm, required: false, default: 'activerecord', enum: ['activerecord','sequel'],
129
+ # desc: 'Type of ORM model to create.'
130
+ # def gmodel(*args)
131
+ # # Because we cannot share the :collection_name argument, we need to do this check here, before
132
+ # # we "parse" it and pass it to the g command
133
+ # unless args.size == 1
134
+ # ::PraxisGen::Model.command_help(shell,:g)
135
+ # exit 1
136
+ # end
137
+
138
+ # model_name,_ = args
139
+ # ::PraxisGen::Model.new([model_name],options).invoke(:g)
140
+ # end
141
+
87
142
  end
88
143
 
89
144
  PraxisGenerator.start(ARGV)
data/lib/praxis.rb CHANGED
@@ -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