praxis 2.0.pre.9 → 2.0.pre.10
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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +11 -0
- data/TODO.md +1 -4
- data/bin/praxis +11 -13
- data/lib/praxis.rb +10 -3
- data/lib/praxis/action_definition.rb +15 -13
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +0 -7
- data/lib/praxis/api_general_info.rb +1 -1
- data/lib/praxis/application.rb +6 -2
- data/lib/praxis/blueprint.rb +357 -0
- data/lib/praxis/bootloader.rb +9 -3
- data/lib/praxis/bootloader_stages/environment.rb +15 -13
- data/lib/praxis/collection.rb +1 -11
- data/lib/praxis/config_hash.rb +44 -0
- data/lib/praxis/docs/{openapi → open_api}/info_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/media_type_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/operation_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/parameter_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/request_body_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/response_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/responses_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/schema_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/server_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/tag_object.rb +0 -0
- data/lib/praxis/docs/open_api_generator.rb +90 -5
- data/lib/praxis/endpoint_definition.rb +273 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +1 -1
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +1 -1
- data/lib/praxis/extensions/field_expansion.rb +3 -36
- data/lib/praxis/extensions/pagination.rb +2 -2
- data/lib/praxis/extensions/pagination/ordering_params.rb +1 -1
- data/lib/praxis/extensions/pagination/pagination_params.rb +6 -4
- data/lib/praxis/field_expander.rb +90 -0
- data/lib/praxis/finalizable.rb +34 -0
- data/lib/praxis/mapper/selector_generator.rb +1 -1
- data/lib/praxis/media_type.rb +3 -68
- data/lib/praxis/plugin_concern.rb +1 -1
- data/lib/praxis/plugins/mapper_plugin.rb +7 -7
- data/lib/praxis/renderer.rb +88 -0
- data/lib/praxis/request.rb +1 -1
- data/lib/praxis/resource_definition.rb +2 -311
- data/lib/praxis/response_definition.rb +2 -10
- data/lib/praxis/response_template.rb +3 -3
- data/lib/praxis/router.rb +2 -2
- data/lib/praxis/routing_config.rb +1 -1
- data/lib/praxis/tasks/api_docs.rb +13 -63
- data/lib/praxis/tasks/routes.rb +1 -1
- data/lib/praxis/types/media_type_common.rb +1 -11
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +0 -1
- data/spec/functional_spec.rb +5 -9
- data/spec/praxis/action_definition_spec.rb +12 -20
- data/spec/praxis/blueprint_spec.rb +373 -0
- data/spec/praxis/bootloader_spec.rb +10 -2
- data/spec/praxis/collection_spec.rb +0 -13
- data/spec/praxis/config_hash_spec.rb +64 -0
- data/spec/praxis/{resource_definition_spec.rb → endpoint_definition_spec.rb} +37 -64
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +4 -6
- data/spec/praxis/extensions/field_expansion_spec.rb +5 -24
- data/spec/praxis/field_expander_spec.rb +149 -0
- data/spec/praxis/media_type_identifier_spec.rb +5 -4
- data/spec/praxis/media_type_spec.rb +4 -93
- data/spec/praxis/renderer_spec.rb +188 -0
- data/spec/praxis/response_definition_spec.rb +0 -31
- data/spec/praxis/response_spec.rb +1 -1
- data/spec/praxis/router_spec.rb +8 -8
- data/spec/praxis/routing_config_spec.rb +3 -3
- data/spec/spec_app/app/controllers/instances.rb +13 -7
- data/spec/spec_app/design/media_types/instance.rb +1 -19
- data/spec/spec_app/design/media_types/volume.rb +1 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -14
- data/spec/spec_app/design/resources/instances.rb +5 -8
- data/spec/spec_app/design/resources/volume_snapshots.rb +1 -1
- data/spec/spec_app/design/resources/volumes.rb +1 -1
- data/spec/support/spec_authorization_plugin.rb +1 -1
- data/spec/support/spec_blueprints.rb +72 -0
- data/spec/support/{spec_resource_definitions.rb → spec_endpoint_definitions.rb} +2 -2
- data/spec/support/spec_media_types.rb +6 -26
- data/tasks/thor/app.rb +8 -34
- data/tasks/thor/example.rb +45 -285
- data/tasks/thor/templates/generator/empty_app/.gitignore +0 -1
- data/tasks/thor/templates/generator/empty_app/Gemfile +7 -23
- data/tasks/thor/templates/generator/empty_app/README.md +1 -1
- data/tasks/thor/templates/generator/empty_app/Rakefile +4 -13
- data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/config/environment.rb +25 -17
- data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/docs/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/docs/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +14 -9
- data/tasks/thor/templates/generator/example_app/.gitignore +1 -0
- data/tasks/thor/templates/generator/example_app/Gemfile +19 -0
- data/tasks/thor/templates/generator/example_app/Rakefile +54 -0
- data/tasks/thor/templates/generator/example_app/app/models/user.rb +6 -0
- data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +17 -0
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +46 -0
- data/tasks/thor/templates/generator/example_app/config.ru +31 -0
- data/tasks/thor/templates/generator/example_app/config/environment.rb +40 -0
- data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +11 -0
- data/tasks/thor/templates/generator/example_app/design/api.rb +18 -0
- data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +37 -0
- data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +26 -0
- data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +18 -0
- data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +42 -0
- data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +37 -0
- metadata +49 -135
- data/lib/api_browser/.bowerrc +0 -3
- data/lib/api_browser/.editorconfig +0 -21
- data/lib/api_browser/Gruntfile.js +0 -581
- data/lib/api_browser/app/index.html +0 -59
- data/lib/api_browser/app/js/app.js +0 -48
- data/lib/api_browser/app/js/controllers/action.js +0 -47
- data/lib/api_browser/app/js/controllers/controller.js +0 -10
- data/lib/api_browser/app/js/controllers/menu.js +0 -93
- data/lib/api_browser/app/js/controllers/trait.js +0 -10
- data/lib/api_browser/app/js/controllers/type.js +0 -24
- data/lib/api_browser/app/js/directives/attribute_description.js +0 -56
- data/lib/api_browser/app/js/directives/attribute_table.js +0 -28
- data/lib/api_browser/app/js/directives/conditional_requirements.js +0 -13
- data/lib/api_browser/app/js/directives/fixed_if_fits.js +0 -38
- data/lib/api_browser/app/js/directives/highlight.js +0 -14
- data/lib/api_browser/app/js/directives/menu_item.js +0 -59
- data/lib/api_browser/app/js/directives/no_container.js +0 -8
- data/lib/api_browser/app/js/directives/readable_list.js +0 -87
- data/lib/api_browser/app/js/directives/request_examples.js +0 -31
- data/lib/api_browser/app/js/directives/type_placeholder.js +0 -30
- data/lib/api_browser/app/js/directives/url.js +0 -15
- data/lib/api_browser/app/js/factories/Configuration.js +0 -12
- data/lib/api_browser/app/js/factories/Documentation.js +0 -61
- data/lib/api_browser/app/js/factories/Example.js +0 -51
- data/lib/api_browser/app/js/factories/PageInfo.js +0 -9
- data/lib/api_browser/app/js/factories/normalize_attributes.js +0 -20
- data/lib/api_browser/app/js/factories/prepare_template.js +0 -15
- data/lib/api_browser/app/js/factories/template_for.js +0 -128
- data/lib/api_browser/app/js/filters/attribute_name.js +0 -10
- data/lib/api_browser/app/js/filters/friendly_json.js +0 -5
- data/lib/api_browser/app/js/filters/has_requirement.js +0 -14
- data/lib/api_browser/app/js/filters/header_info.js +0 -9
- data/lib/api_browser/app/js/filters/is_empty.js +0 -8
- data/lib/api_browser/app/js/filters/markdown.js +0 -6
- data/lib/api_browser/app/js/filters/resource_name.js +0 -5
- data/lib/api_browser/app/js/filters/tag_requirement.js +0 -13
- data/lib/api_browser/app/sass/modules/_body.scss +0 -40
- data/lib/api_browser/app/sass/modules/_cloke.scss +0 -8
- data/lib/api_browser/app/sass/modules/_header.scss +0 -10
- data/lib/api_browser/app/sass/modules/_nav.scss +0 -7
- data/lib/api_browser/app/sass/modules/_sidebar.scss +0 -134
- data/lib/api_browser/app/sass/modules/_switch.scss +0 -55
- data/lib/api_browser/app/sass/modules/_table.scss +0 -13
- data/lib/api_browser/app/sass/praxis.scss +0 -70
- data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +0 -774
- data/lib/api_browser/app/views/action.html +0 -97
- data/lib/api_browser/app/views/builtin/field-selector.html +0 -24
- data/lib/api_browser/app/views/controller.html +0 -55
- data/lib/api_browser/app/views/directives/attribute_description.html +0 -2
- data/lib/api_browser/app/views/directives/attribute_description/default.html +0 -2
- data/lib/api_browser/app/views/directives/attribute_description/example.html +0 -13
- data/lib/api_browser/app/views/directives/attribute_description/headers.html +0 -8
- data/lib/api_browser/app/views/directives/attribute_description/member_options.html +0 -4
- data/lib/api_browser/app/views/directives/attribute_description/values.html +0 -14
- data/lib/api_browser/app/views/directives/attribute_table.html +0 -17
- data/lib/api_browser/app/views/directives/menu_item.html +0 -8
- data/lib/api_browser/app/views/directives/url.html +0 -3
- data/lib/api_browser/app/views/examples/general.html +0 -26
- data/lib/api_browser/app/views/home.html +0 -5
- data/lib/api_browser/app/views/layout.html +0 -8
- data/lib/api_browser/app/views/menu.html +0 -42
- data/lib/api_browser/app/views/navbar.html +0 -9
- data/lib/api_browser/app/views/trait.html +0 -13
- data/lib/api_browser/app/views/type.html +0 -6
- data/lib/api_browser/app/views/type/details.html +0 -33
- data/lib/api_browser/app/views/types/embedded/array.html +0 -2
- data/lib/api_browser/app/views/types/embedded/default.html +0 -12
- data/lib/api_browser/app/views/types/embedded/field-selector.html +0 -13
- data/lib/api_browser/app/views/types/embedded/links.html +0 -11
- data/lib/api_browser/app/views/types/embedded/requirements.html +0 -6
- data/lib/api_browser/app/views/types/embedded/single_req.html +0 -9
- data/lib/api_browser/app/views/types/embedded/struct.html +0 -14
- data/lib/api_browser/app/views/types/label/link.html +0 -1
- data/lib/api_browser/app/views/types/label/primitive.html +0 -1
- data/lib/api_browser/app/views/types/label/primitive_collection.html +0 -1
- data/lib/api_browser/app/views/types/label/type.html +0 -1
- data/lib/api_browser/app/views/types/label/type_collection.html +0 -1
- data/lib/api_browser/app/views/types/main/array.html +0 -22
- data/lib/api_browser/app/views/types/main/default.html +0 -23
- data/lib/api_browser/app/views/types/main/hash.html +0 -23
- data/lib/api_browser/app/views/types/standalone/array.html +0 -3
- data/lib/api_browser/app/views/types/standalone/default.html +0 -18
- data/lib/api_browser/app/views/types/standalone/struct.html +0 -2
- data/lib/api_browser/bower_template.json +0 -41
- data/lib/api_browser/package-lock.json +0 -7110
- data/lib/api_browser/package.json +0 -43
- data/lib/praxis/docs/generator.rb +0 -243
- data/lib/praxis/docs/link_builder.rb +0 -30
- data/lib/praxis/links.rb +0 -135
- data/lib/praxis/types/multipart.rb +0 -109
- data/spec/api_browser/directives/type_placeholder_spec.js +0 -134
- data/spec/api_browser/factories/configuration_spec.js +0 -32
- data/spec/api_browser/factories/documentation_spec.js +0 -100
- data/spec/api_browser/factories/normalize_attributes_spec.js +0 -92
- data/spec/api_browser/factories/template_for_spec.js +0 -67
- data/spec/api_browser/filters/attribute_name_spec.js +0 -23
- data/spec/praxis/types/multipart_spec.rb +0 -112
- data/tasks/thor/templates/generator/empty_app/.rspec +0 -1
- data/tasks/thor/templates/generator/empty_app/Guardfile +0 -3
- data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +0 -57
- data/tasks/thor/templates/generator/empty_app/docs/app.js +0 -1
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dd76d8bd13954d5607d009ee37ff7fc1a8ef8a2c6c8c87544dddaee332d984bf
|
|
4
|
+
data.tar.gz: 719bffed7ea98fe42d7c620cd4377c4201c9d23e49a9777e981647d671b39ffd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18e87e4563faa7a1ffbba174634f1ebabe57320b8324e4ac23a711f5785906482412db711c83308ff87264d48cc496f8bd118b5a5ac77f1d6bd8f09e543f90ed
|
|
7
|
+
data.tar.gz: 9da869a6acecf7c04273ef7dc0519be0cf8b3d00c29df16c851df04f5ea52d7cf4d62123e25533b8887c0a30b940155abfbd812f4fe2b347d1139df91e3e124f
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -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:
|
|
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.
|
|
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, :
|
|
79
|
+
desc_for "example APP_NAME", ::PraxisGen::Example, :example
|
|
78
80
|
def example(app_name)
|
|
79
|
-
::PraxisGen::Example.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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)
|
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 :
|
|
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,
|
|
34
|
+
def initialize(name, endpoint_definition, **opts, &block)
|
|
35
35
|
@name = name
|
|
36
|
-
@
|
|
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 =
|
|
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 =
|
|
49
|
-
api_info = ApiDefinition.instance.info(
|
|
48
|
+
version = endpoint_definition.version
|
|
49
|
+
api_info = ApiDefinition.instance.info(endpoint_definition.version)
|
|
50
50
|
|
|
51
|
-
route_base = "#{api_info.base_path}#{
|
|
52
|
-
prefix = Array(
|
|
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
|
-
|
|
56
|
+
endpoint_definition.traits.each do |trait|
|
|
57
57
|
self.trait(trait)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
|
|
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 #{
|
|
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,'')
|
data/lib/praxis/application.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Praxis
|
|
|
8
8
|
|
|
9
9
|
attr_reader :router
|
|
10
10
|
attr_reader :controllers
|
|
11
|
-
attr_reader :
|
|
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
|
-
@
|
|
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
|