Class: Hoodoo::Services::Interface
- Inherits:
-
Object
- Object
- Hoodoo::Services::Interface
- Defined in:
- lib/hoodoo/services/services/interface.rb
Overview
Service implementation authors subclass this to describe the interface that they implement for a particular Resource, as documented in the Loyalty Platform API.
See class method ::interface for details.
Defined Under Namespace
Class Attribute Summary collapse
-
.actions ⇒ Object
readonly
Supported action methods as a Set of symbols with one or more of
:list
,:show
,:create
,:update
or:delete
. -
.additional_permissions ⇒ Object
readonly
A Hash, keyed by String equivalents of the Symbols in Hoodoo::Services::Middleware::ALLOWED_ACTIONS, where the values are Hoodoo::Services::Permissions instances describing extended permissions for the related action.
-
.embeds ⇒ Object
readonly
Array of strings listing allowed embeddable things.
-
.endpoint ⇒ Object
readonly
Endpoint path as declared by service, without preceding “/”, possibly as a symbol - e.g.
-
.errors_for ⇒ Object
readonly
A Hoodoo::ErrorDescriptions instance describing all errors that the interface might return, including the default set of platform and generic errors.
-
.implementation ⇒ Object
readonly
Implementation class for the service.
-
.public_actions ⇒ Object
readonly
Public action methods as a Set of symbols with one or more of
:list
,:show
,:create
,:update
or:delete
. -
.resource ⇒ Object
readonly
Name of the resource the interface addresses as a symbol, e.g.
-
.secure_log_for ⇒ Object
readonly
Secure log actions set by #secure_log_for - see that call for details.
-
.to_create ⇒ Object
readonly
A Hoodoo::Presenters::Object instance describing the schema for client JSON coming in for calls that create instances of the resource that the service’s interface is addressing.
-
.to_update ⇒ Object
readonly
A Hoodoo::Presenters::Object instance describing the schema for client JSON coming in for calls that modify instances of the resource that the service’s interface is addressing.
-
.version ⇒ Object
readonly
Major version of interface as an integer.
Class Method Summary collapse
-
.to_list ⇒ Object
A Hoodoo::Services::Interface::ToList instance describing the list parameters for the interface as a Set of Strings.
Instance Method Summary collapse
-
#actions(*supported_actions) ⇒ Object
List the actions that the service implementation supports.
-
#additional_permissions_for(action) {|p| ... } ⇒ Object
Declare additional permissions that you require for a given action.
-
#embeds(*embeds) ⇒ Object
An array of supported embed keys (as per documentation, so singular or plural as per resource interface descriptions in the Loyalty Platform API).
-
#endpoint(uri_path_fragment, implementation_class) ⇒ Object
Mandatory part of the interface DSL.
-
#errors_for(domain, &block) ⇒ Object
Declares custom errors that are part of this defined interface.
-
#public_actions(*public_actions) ⇒ Object
List any actions which are public - NOT PROTECTED BY SESSIONS.
-
#secure_log_for(secure_log_actions = {}) ⇒ Object
Set secure log actions.
-
#to_create(&block) ⇒ Object
Optional description of the JSON parameters (schema) that the interface’s implementation requires for calls creating resource instances.
-
#to_list(&block) ⇒ Object
Specify parameters related to common index parameters.
-
#to_update(&block) ⇒ Object
As #to_create, but applies when modifying existing resource instances.
-
#update_same_as_create ⇒ Object
Declares that the expected JSON fields described in a #to_create call are the same as those required for modifying resources too.
-
#version(major_version) ⇒ Object
Declare the major version of the interface being implemented.
Class Attribute Details
.actions ⇒ Object
Supported action methods as a Set of symbols with one or more of :list
, :show
, :create
, :update
or :delete
. The presence of a Symbol indicates a supported action. If empty, no actions are supported. The default is for all actions to be present in the Set.
843 844 845 |
# File 'lib/hoodoo/services/services/interface.rb', line 843 def actions @actions end |
.additional_permissions ⇒ Object
A Hash, keyed by String equivalents of the Symbols in Hoodoo::Services::Middleware::ALLOWED_ACTIONS, where the values are Hoodoo::Services::Permissions instances describing extended permissions for the related action. See ::additional_permissions_for.
913 914 915 |
# File 'lib/hoodoo/services/services/interface.rb', line 913 def @additional_permissions end |
.embeds ⇒ Object
Array of strings listing allowed embeddable things. Each string matches the split up comma-separated value for query string _embed
or _reference
keys. For example:
...&_embed=foo,bar
…would be valid provided there was an embedding declaration such as:
:foo, :bar
…which would in turn lead this accessor to return:
[ 'foo', 'bar' ]
873 874 875 |
# File 'lib/hoodoo/services/services/interface.rb', line 873 def @embeds end |
.endpoint ⇒ Object
Endpoint path as declared by service, without preceding “/”, possibly as a symbol - e.g. :products
for “/products” as an implied endpoint.
820 821 822 |
# File 'lib/hoodoo/services/services/interface.rb', line 820 def endpoint @endpoint end |
.errors_for ⇒ Object
A Hoodoo::ErrorDescriptions instance describing all errors that the interface might return, including the default set of platform and generic errors. If nil, there are no additional error codes beyond the default set.
905 906 907 |
# File 'lib/hoodoo/services/services/interface.rb', line 905 def errors_for @errors_for end |
.implementation ⇒ Object
Implementation class for the service. An Hoodoo::Services::Implementation subclass - the class, not an instance of it.
836 837 838 |
# File 'lib/hoodoo/services/services/interface.rb', line 836 def implementation @implementation end |
.public_actions ⇒ Object
Public action methods as a Set of symbols with one or more of :list
, :show
, :create
, :update
or :delete
. The presence of a Symbol indicates an action open to the public and not subject to session security. If empty, all actions are protected by session security. The default is an empty Set.
851 852 853 |
# File 'lib/hoodoo/services/services/interface.rb', line 851 def public_actions @public_actions end |
.resource ⇒ Object
Name of the resource the interface addresses as a symbol, e.g. :Product
.
830 831 832 |
# File 'lib/hoodoo/services/services/interface.rb', line 830 def resource @resource end |
.secure_log_for ⇒ Object
Secure log actions set by #secure_log_for - see that call for details. The default is an empty Hash.
856 857 858 |
# File 'lib/hoodoo/services/services/interface.rb', line 856 def secure_log_for @secure_log_for end |
.to_create ⇒ Object
A Hoodoo::Presenters::Object instance describing the schema for client JSON coming in for calls that create instances of the resource that the service’s interface is addressing. If nil
, arbitrary data is acceptable (the implementation becomes entirely responsible for data validation).
890 891 892 |
# File 'lib/hoodoo/services/services/interface.rb', line 890 def to_create @to_create end |
.to_update ⇒ Object
A Hoodoo::Presenters::Object instance describing the schema for client JSON coming in for calls that modify instances of the resource that the service’s interface is addressing. If nil
, arbitrary data is acceptable (the implementation becomes entirely responsible for data validation).
898 899 900 |
# File 'lib/hoodoo/services/services/interface.rb', line 898 def to_update @to_update end |
.version ⇒ Object
Major version of interface as an integer. All service endpoint routes have “vversion/” as a prefix, e.g. “/v1/products”.
825 826 827 |
# File 'lib/hoodoo/services/services/interface.rb', line 825 def version @version end |
Class Method Details
.to_list ⇒ Object
A Hoodoo::Services::Interface::ToList instance describing the list parameters for the interface as a Set of Strings. See also Hoodoo::Services::Interface::ToListDSL.
879 880 881 882 |
# File 'lib/hoodoo/services/services/interface.rb', line 879 def to_list @to_list ||= Hoodoo::Services::Interface::ToList.new @to_list end |
Instance Method Details
#actions(*supported_actions) ⇒ Object
List the actions that the service implementation supports. If you don’t call this, the middleware assumes that all actions are available; else it only calls for supported actions. If you declared an empty array, your implementation would never be called.
- *supported_actions
-
One or more from
:list
,:show
,:create
,:update
and:delete
. Always use symbols, not strings. An exception is raised if unrecognised actions are given.
Example:
actions :list, :show
373 374 375 376 377 378 379 380 381 382 |
# File 'lib/hoodoo/services/services/interface.rb', line 373 def actions( *supported_actions ) supported_actions.map! { | item | item.to_sym } invalid = supported_actions - Hoodoo::Services::Middleware::ALLOWED_ACTIONS unless invalid.empty? raise "Hoodoo::Services::Interface#actions does not recognise one or more actions: '#{ invalid.join( ', ' ) }'" end self.class.send( :actions=, Set.new( supported_actions ) ) end |
#additional_permissions_for(action) {|p| ... } ⇒ Object
Declare additional permissions that you require for a given action.
If the implementation of a resource endpoint involves making calls out to other resources, then you need to consider how authorisation is granted to those other resources.
The Hoodoo::Services::Session instance for the inbound external caller carries a Hoodoo::Services::Permission instance describing the actions that the caller is permitted to do. The middleware enforces these permissions, so that a resource implementation won’t be called at all unless the caller has permission to do so.
These permissions continue to apply during inter-resource calls. The wider session context is always applied. So, if one resource calls another resource, either:
-
The inbound API caller’s session must have all necessary permissions for both the resource it is actually directly calling, and for any actions in any resources that the called resource in turn calls (and so-on, for any chain of resources).
…or…
-
The resource uses this
additional_permissions_for
method to declare up-front that it will require the described permissions when a particular action is performed on it. When an inter-resource call is made, a temporary internal-only session is constructed that merges the permissions of the inbound caller with the additional permissions requested by the resource. The downstream called resource needs no special case code at all - it just sees a valid session with valid permissions and does what the upstream resource asked of it.
For example, suppose a resource Clock returns both a time and a date, by calling out to the Time and Date resources. One option is that the inbound caller must have show
action permissions for all of Clock, Time and Date; if any of those are missing, then an attempt to call show
on the Clock resource would result in a 403 response.
The other option is for Clock’s interface to declare its requirements:
( :show ) do | p |
p.set_resource( :Time, :show, Hoodoo::Services::Permissions::ALLOW )
p.set_resource( :Date, :show, Hoodoo::Services::Permissions::ALLOW )
end
Suppose you could create Clock instances for some reason, but there was an audit trail for this; Clock must create an Audit entry itself, but you don’t want to expose this ability to external callers through their session permissions; so, just declare your additional permissions for that specific inter-service case:
( :create ) do | p |
p.set_resource( :Audit, :create, Hoodoo::Services::Permissions::ALLOW )
end
The call says which action in the declaring _interface’s_ resource is a target. The block takes a single parameter; this is a default initialisation Hoodoo::Services::Permissions instance. Use that object’s methods to set up whatever permissions you need in other resources, to successfully process the action in question. You only need to describe the resources you immediately call, not the whole chain - if “this” resource calls another, then it’s up to the other resource to in turn describe additional permissions should it make its own set of downstream calls to further resource endpoints.
Setting default permissions or especially the default permission fallback inside the block is possible but VERY STRONGLY DISCOURAGED. Instead, precisely describe the downstream resources, actions and permissions that are required.
Note an important restriction - public actions (see ::public_actions) cannot be augmented in this way. A public action in one resource can only ever call public actions in other resources. This is because no session is needed at all to call a public action; calling into a protected action in another resource from this context would require invention of a full caller context which would be entirely invented and could represent an accidental (and significant) security hole.
If you call this method for the same action more than once, the last call will be the one that takes effect - each call overwrites the results of any previous call made for the same action.
Parameters are:
action
-
The action in this interface which will require the additional permissions to be described. Pass a Symbol or equivalent String from the list in Hoodoo::Services::Middleware::ALLOWED_ACTIONS.
- &block
-
Block which is passed a new, default state Hoodoo::Services::Permissions instance; make method calls on this instance to describe the required permissions.
726 727 728 729 730 731 732 733 734 735 736 737 738 739 |
# File 'lib/hoodoo/services/services/interface.rb', line 726 def ( action, &block ) action = action.to_s unless block_given? raise 'Hoodoo::Services::Interface#additional_permissions_for must be passed a block' end p = Hoodoo::Services::Permissions.new yield( p ) = self.class.() || {} [ action ] = p self.class.send( :additional_permissions=, ) end |
#embeds(*embeds) ⇒ Object
An array of supported embed keys (as per documentation, so singular or plural as per resource interface descriptions in the Loyalty Platform API). Things which can be embedded can also be referenced, via the _embed
and _reference
query string keys.
The middleware uses the list to reject requests from clients which ask for embedded or referenced entities that were not listed by the interface. If you don’t call here, or call here with an empty array, no embedding or referencing will be allowed for calls to the service implementation.
embed
-
Array of permitted embeddable entity names, as symbols or strings. The order of array entries is arbitrary.
Example: An interface permits lists that request embedding or referencing of “vouchers”, “balances” and “member”:
:vouchers, :balances, :member
As a result, #embeds would return:
[ 'vouchers', 'balances', 'member' ]
496 497 498 |
# File 'lib/hoodoo/services/services/interface.rb', line 496 def ( * ) self.class.send( :embeds=, .map { | item | item.to_s } ) end |
#endpoint(uri_path_fragment, implementation_class) ⇒ Object
Mandatory part of the interface DSL. Declare the interface’s URL endpoint and the Hoodoo::Services::Implementation subclass to be invoked when client requests are sent to a URL matching the endpoint.
No two interfaces can use the same endpoint within a service application, unless the describe a different interface version - see #version.
Example:
endpoint :estimations, PurchaseImplementation
uri_path_fragment
-
Path fragment to match at the start of a URL path, as a symbol or string, excluding leading “/”. The URL path matches the fragment if the path starts with a “/”, then matches the fragment exactly, then is followed by either “.”, another “/”, or the end of the path string. For example, a fragment of
:products
matches all paths out of/products
,/products.json
or/products/22
, but does not match/products_and_things
. implementation_class
-
The Hoodoo::Services::Implementation subclass (the class itself, not an instance of it) that should be used when a request matching the path fragment is received.
333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/hoodoo/services/services/interface.rb', line 333 def endpoint( uri_path_fragment, implementation_class ) # http://www.ruby-doc.org/core-2.2.3/Module.html#method-i-3C # unless implementation_class < Hoodoo::Services::Implementation raise "Hoodoo::Services::Interface#endpoint must provide Hoodoo::Services::Implementation subclasses, but '#{ implementation_class }' was given instead" end self.class.send( :endpoint=, uri_path_fragment ) self.class.send( :implementation=, implementation_class ) end |
#errors_for(domain, &block) ⇒ Object
Declares custom errors that are part of this defined interface. This calls directly through to Hoodoo::ErrorDescriptions#errors_for, so see that for details.
A service should usually define only a single domain of error using one call to #errors_for, but techncially can make as many calls for as many domains as required. Definitions are merged.
domain
-
Domain, e.g. ‘purchase’, ‘transaction’ - see Hoodoo::ErrorDescriptions#errors_for for details.
- &block
-
Code block making Hoodoo::ErrorDescriptions DSL calls.
Example:
errors_for 'transaction' do
error 'duplicate_transaction', status: 409, message: 'Duplicate transaction', :required => [ :client_uid ]
end
623 624 625 626 627 628 629 630 631 |
# File 'lib/hoodoo/services/services/interface.rb', line 623 def errors_for( domain, &block ) descriptions = self.class.errors_for if descriptions.nil? descriptions = self.class.send( :errors_for=, Hoodoo::ErrorDescriptions.new ) end descriptions.errors_for( domain, &block ) end |
#public_actions(*public_actions) ⇒ Object
List any actions which are public - NOT PROTECTED BY SESSIONS. For public actions, no X-Session-ID or similar header is consulted and no session data will be associated with your Hoodoo::Services::Context instance when action methods are called.
Use with great care!
Note that if the implementation of a public action needs to call other resources, it can only ever call them if those actions in those other resources are also public. The implementation of a public action is prohibited from making calls to protected actions in other resources.
- *public_actions
-
One or more from
:list
,:show
,:create
,:update
and:delete
. Always use symbols, not strings. An exception is raised if unrecognised actions are given.
402 403 404 405 406 407 408 409 410 411 |
# File 'lib/hoodoo/services/services/interface.rb', line 402 def public_actions( *public_actions ) public_actions.map! { | item | item.to_sym } invalid = public_actions - Hoodoo::Services::Middleware::ALLOWED_ACTIONS unless invalid.empty? raise "Hoodoo::Services::Interface#public_actions does not recognise one or more actions: '#{ invalid.join( ', ' ) }'" end self.class.send( :public_actions=, Set.new( public_actions ) ) end |
#secure_log_for(secure_log_actions = {}) ⇒ Object
Set secure log actions.
secure_log_actions
-
A Hash, described below.
The given Hash keys are names of actions as Symbols: :list
, :show
, :create
, :update
or :delete
. Values are :request
, :response
or :both
. For a given action targeted at this resource:
-
A key of
:request
means that API call-related Hoodoo automatic logging will exclude body data for the inbound request, but still include body data in the response. Example: A POST to a Login resource includes a password which you don’t want logged, but the response data doesn’t quote the password back so is “safe”. The secure log actions Hash for the Login resource’s interface would include:create => :request
. -
A key of
:response
means that API call-related Hoodoo automatic logging will exclude body data for the outbound response, but still include body data in the request. Example: A POST to a Caller resource creates a Caller with a generated authentication secret that’s only exposed in the POST’s response. The inbound data used to create that Caller can be safely logged, but the authentication secret is sensitive and shouldn’t be recorded. The secure log actions Hash for the Caller resource’s interface would include:create => :response
.ERROR RESPONSES ARE STILL LOGGED because that’s useful data; so make sure that if you generate any custom errors in your service that secure data is not contained within them.
-
A key of
both
has the same result as both:request
and:response
, so body data is never logged. It’s hard to come up with good examples of resources where both the incoming data is sensitive and the outgoing data is sensitive but the option is included for competion, as someone out there will need it.
Example: The request body data sent by a caller into a resource’s :create
action will not be logged:
secure_log_for( { :create => :request } )
Example: Neither the request data sent by a caller, nor the response data sent back, will be logged for an :update
action:
secure_log_for( { :update => :both } )
The default is an empty Hash; all actions have both inbound request body data and outbound response body data logged by Hoodoo.
462 463 464 465 466 467 468 469 470 471 |
# File 'lib/hoodoo/services/services/interface.rb', line 462 def secure_log_for( secure_log_actions = {} ) secure_log_actions = Hoodoo::Utilities.symbolize( secure_log_actions ) invalid = secure_log_actions.keys - Hoodoo::Services::Middleware::ALLOWED_ACTIONS unless invalid.empty? raise "Hoodoo::Services::Interface#secure_log_for does not recognise one or more actions: '#{ invalid.join( ', ' ) }'" end self.class.send( :secure_log_for=, secure_log_actions ) end |
#to_create(&block) ⇒ Object
Optional description of the JSON parameters (schema) that the interface’s implementation requires for calls creating resource instances. The block uses the DSL from Hoodoo::Presenters::Object, so you can specify basic object things like string
, or higher level things like type
or resource
.
If a call comes into the middleware from a client which contains body data that doesn’t validate according to your schema, it’ll be rejected before even getting as far as your interface implementation.
Default values for fields where present are for rendering only; they are not injected into the inbound body for (say) persistence at database levels. A returned, rendered representation based on the same schema would have the default values present only. If you need default values at the persistence layer too, define them there too with whatever mechanism is most appropriate for your chosen persistence approach.
The Hoodoo::Presenters::Object#internationalised DSL method can be called within your block harmlessly, but it has no side effects. Any resource interface that can take internationalised data for creation (or modification) must already have an internationalised representation, so the standard resources in the Hoodoo::Data::Resources collection will already have declared that internationalisation applies.
Example 1:
to_create do
string :name, :length => 32, :required => true
text :description
end
Example 2: With a resource
to_create do
resource Product # Fields are *inline*
end
- &block
-
Block, passed to Hoodoo::Presenters::Object, describing the fields used for resource creation.
557 558 559 560 561 562 |
# File 'lib/hoodoo/services/services/interface.rb', line 557 def to_create( &block ) obj = Class.new( Hoodoo::Presenters::Base ) obj.schema( &block ) self.class.send( :to_create=, obj ) end |
#to_list(&block) ⇒ Object
Specify parameters related to common index parameters. The block contains calls to the DSL described by Hoodoo::Services::Interface::ToListDSL. The default values should be described by your platform’s API - hard-coded at the time of writing as:
limit 50
sort :created_at => [ :desc, :asc ]
search nil
filter nil
510 511 512 513 514 515 |
# File 'lib/hoodoo/services/services/interface.rb', line 510 def to_list( &block ) Hoodoo::Services::Interface::ToListDSL.new( self.class.instance_variable_get( '@to_list' ), &block ) end |
#to_update(&block) ⇒ Object
As #to_create, but applies when modifying existing resource instances. To avoid repeating yourself, if your modification and creation parameter requirements are identical, call #update_same_as_create.
The “required” flag is ignored for updates, because an omitted field for an update to an existing resource instance simply means “do not change the current value”. As with #to_create, default values have relevance to the rendering stage only and have no effect here.
- &block
-
Block, passed to Hoodoo::Presenters::Object, describing the fields used for resource modification.
576 577 578 579 580 581 582 583 584 585 586 587 588 589 |
# File 'lib/hoodoo/services/services/interface.rb', line 576 def to_update( &block ) obj = Class.new( Hoodoo::Presenters::Base ) obj.schema( &block ) # When updating, 'required' fields in schema aren't required; you just # omit a field to avoid changing its value. Walk the to-update schema # graph stripping out any such problematic attributes. # obj.walk do | property | property.required = false end self.class.send( :to_update=, obj ) end |
#update_same_as_create ⇒ Object
Declares that the expected JSON fields described in a #to_create call are the same as those required for modifying resources too.
Example:
update_same_as_create
…and that’s all. There are no parameters or blocks needed.
600 601 602 |
# File 'lib/hoodoo/services/services/interface.rb', line 600 def update_same_as_create self.send( :to_update, & self.class.to_create().get_schema_definition() ) end |
#version(major_version) ⇒ Object
Declare the major version of the interface being implemented. All service endpoints appear at “/vversion/endpoint” relative to whatever root an edge layer defines. If a service interface does not specifiy its version, 1
is assumed.
Two interfaces can exist on the same endpoint provided their versions are different since the resulting route to reach them will be different too.
version
-
Integer major version number, e.g
2
.
355 356 357 |
# File 'lib/hoodoo/services/services/interface.rb', line 355 def version( major_version ) self.class.send( :version=, major_version.to_s.to_i ) end |