Skip to content

Commit 9aa0b55

Browse files
authored
feat: add call to endpoint & authenticators (#6)
Encourage the use of classes for endpoints.
1 parent 4282267 commit 9aa0b55

33 files changed

+357
-334
lines changed

README.md

+25-38
Original file line numberDiff line numberDiff line change
@@ -49,40 +49,23 @@ end
4949

5050
The key thing to note here is that the `CoreAPI::Base` reference is provided as a string rather than the constant itself. You can provide the constant here but using a string will ensure that API can be reloaded in development when classes are unloaded.
5151

52-
### Creating a controller and endpoint
52+
### Creating an endpoint
5353

54-
A controller is a collection of actions (or endpoints) that will perform actions and return data as appropriate. An API can have as many controllers as you need and a controller can have as many endpoints as needed.
55-
56-
Begin by creating a new `controllers` directory in your `app/apis/core_api` directory. Within that, add a file for your first controller. In this example, we'll make a controller for managing products and thus we'll call it the `Products` controller. This controller should inherit from `Rapid::Controller`.
54+
An endpoint is an action that can be invoked by your consumers. It might return a list, create an resource or anything that takes your fancy. Begin by creating a new `endpoints` director in your `app/apis/core_api` directory. We'll begin by making an endpoint that will simply return a list of products that we sell. Make a file called `product_list_endpoint.rb` in your new `endpoints` directory.
5755

5856
```ruby
5957
module CoreAPI
60-
module Controllers
61-
module Products < Rapid::Controller
58+
module Endpints
59+
class ProductListEndpoint < Rapid::Endpoint
6260

63-
name 'Products'
64-
description 'Allows you to list & manages products in the product database'
61+
name 'List products'
62+
description 'Returns a list of all product names in our catalogue'
6563

66-
end
67-
end
68-
end
69-
```
70-
71-
As with the API, this is a very basic implementation of a controller. A controller isn't much use without an endpoint through so we can add an endpoint here.
64+
field :product_names, [:string]
7265

73-
```ruby
74-
module CoreAPI
75-
module Controllers
76-
module Products < Rapid::Controller
77-
78-
endpoint :list do
79-
name 'List products'
80-
description 'Returns a full list of products'
81-
field :products, [:string]
82-
action do
83-
product_names = Products.all.map(&:name)
84-
response.add_field :products, product_names
85-
end
66+
def call
67+
product_names = Product.order(:name).pluck(:name)
68+
response.add_field :product_names, product_names
8669
end
8770

8871
end
@@ -92,19 +75,17 @@ end
9275

9376
This is a very simple endpoint. Walking through each section...
9477

95-
- Firstly, we define the name of the endpoint which, in this case, is `list`. This will be addressed as `products/list` when requests are made to it.
96-
97-
- Then we define the name and description for it. This will appear in the schema & documentation.
78+
- We begin by defining the name and description for it. This will appear in the schema & documentation.
9879

9980
- Then we add a field which we will expect to be returned when this action is invoked. In this case, we're creating a field called `products` and specifying that it will be an array of strings that will be returned.
10081

101-
- Then we define an action which will actually be executed when this endpoint is executed. This action has access to the request and the response. The `request` object contains information about the request being made and the `response` object allows you to influence what is returned to the consumer.
82+
- Then we define the `call` method which will actually be executed when this endpoint is called. In here, you have access to the request and the response. The `request` object contains information about the request being made and the `response` object allows you to influence what is returned to the consumer.
10283

103-
- Finally, we use `response.add_field` to add data for the `products` field that we defined earlier. In this case, an array of product names.
84+
- Finally, we use `response.add_field` to add data for the `product_names` field that we defined earlier. In this case, an array of product names.
10485

10586
#### A note about types
10687

107-
When you define a field (or an argument) you must define a `type`. A type is what type of object that the consumer can expect to receive (or the server will expect to receive in the case of arguments). A type can be provided as a symbol to reference a scalar or a class that inherits from `Rapid::Scalar` (for scalars), `Rapid::Object` (for objects), `Rapid::Enum` (for enums) or `Rapid::Polymorph` (for polymorphs).
88+
When you define a field (or an argument) you must define a `type`. A type is what type of object that the consumer can expect to receive (or is expected to send in the case of arguments). A type can be provided as a symbol to reference a known scaler, or a class that inherits from `Rapid::Scalar` (for scalars), `Rapid::Object` (for objects), `Rapid::Enum` (for enums) or `Rapid::Polymorph` (for polymorphs).
10889

10990
The following scalars are built-in:
11091

@@ -125,7 +106,7 @@ module CoreAPI
125106
class Base < Rapid::API
126107

127108
routes do
128-
get 'products', controller: Controllers::Products, endpoint: :list
109+
get 'products', endpoint: Endpoints::ListProductsEndpoint
129110
end
130111

131112
end
@@ -176,16 +157,22 @@ By default, Rapid will try to find a value for your fields by calling a method n
176157
Once you have created your object class, you will need to update your endpoint to reference the object.
177158

178159
```ruby
179-
endpoint :list do
160+
class ProductListEndpoint < Rapid::Endpoint
161+
162+
# ...
163+
180164
field :products, [Objects::Product]
181-
action do |request, response|
182-
response.add_field :products, Products.all.to_a
165+
166+
def call
167+
products = Product.order(:name)
168+
response.add_field :products, products.to_a
183169
end
170+
184171
end
185172
```
186173

187174
If you make the request now, you should receive an array of objects (hashes) rather than strings now.
188175

189176
## Further reading
190177

191-
Take a look through the docs folder
178+
Take a look through the docs folder.

doc/arguments.md

+34-22
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ Arguments are the name given to any input that is being provided by the consumer
77
At the most basic an argument will likely be a scalar value (a string, integer, boolean etc...) and will be defined as so on an endpoint:
88

99
```ruby
10-
endpoint :create do
10+
class MyEndpoint < Rapid::Endpoint
11+
1112
argument :name, :string, required: true
1213
argument :age, :integer, required: true
1314
argument :superhuman, :boolean
15+
1416
end
1517
```
1618

@@ -29,11 +31,14 @@ The consumer will provide arguments in one of two ways usually - they'll be prov
2931
Arguments are available on the `request` object within the `action` block of an action. For example:
3032

3133
```ruby
32-
endpoint :create do
34+
class MyEndpoint < Rapid::Endpoint
35+
3336
argument :name, :string, required: true
34-
action do
37+
38+
def call
3539
request.arguments[:name] # => 'Adam'
3640
end
41+
3742
end
3843
```
3944

@@ -42,11 +47,14 @@ end
4247
If you wish to receive an array of items from a consumer, you can specify that an argument is an array rather than a flat object.
4348

4449
```ruby
45-
endpoint :create do
50+
class MyEndpoint < Rapid::Endpoint
51+
4652
argument :names, [:string]
53+
4754
action do
4855
request.arguments[:names] # => ['Adam', 'Dave', 'Charlie']
4956
end
57+
5058
end
5159
```
5260

@@ -56,22 +64,25 @@ Argument sets provide you with an option to define a set of multiple arguments t
5664

5765
```ruby
5866
class UserProperties < Rapid::ArgumentSet
67+
5968
argument :name, :string, required: true
6069
argument :date_of_birth, :date
6170
argument :hair_color, :string
6271
argument :favourite_color, :string
6372
argument :favourite_number, :integer
73+
6474
end
6575

66-
class Users < Rapid::Controller
67-
endpoint :create do
68-
argument :user, UserProperties, required: true
69-
action do
70-
request.arguments[:user][:name]
71-
# or...
72-
request.arguments.dig(:user, :name)
73-
end
76+
class MyEndpoint < Rapid::Endpoint
77+
78+
argument :user, UserProperties, required: true
79+
80+
action do
81+
request.arguments[:user][:name]
82+
# or...
83+
request.arguments.dig(:user, :name)
7484
end
85+
7586
end
7687
```
7788

@@ -123,18 +134,19 @@ class UserLookup < Rapid::LookupArgumentSet
123134

124135
end
125136

126-
class Users < Rapid::Controller
127-
endpoint :info do
128-
argument :user, UserLookup, required: true
129-
action do
130-
# This will return the plain set as a normal argument set would.
131-
user_set = request.arguments[:user]
137+
class InfoEndpoint < Rapid::Endpoint
132138

133-
# Calling `resolve` on this will actually resolve the user to
134-
# the object as defined by the resolver.
135-
user = request.arguments[:user].resolve
136-
end
139+
argument :user, UserLookup, required: true
140+
141+
def call
142+
# This will return the plain set as a normal argument set would.
143+
user_set = request.arguments[:user]
144+
145+
# Calling `resolve` on this will actually resolve the user to
146+
# the object as defined by the resolver.
147+
user = request.arguments[:user].resolve
137148
end
149+
138150
end
139151
```
140152

doc/authentication.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ module CoreAPI
2424
field :given_token, :string
2525
end
2626

27-
action do
27+
def call
2828
given_token_string = request.headers['Authorization'].sub(/\ABearer /, '')
2929
token = APIToken.authenticate(given_token_string)
3030
if token.nil?
@@ -43,7 +43,7 @@ Let's take a look through this line by line:
4343
- Firstly, we define the name & description for the authenticator. This is used for documentation.
4444
- Next, we choose the type of authenticator. Rapid only currently supports `:bearer`. This is only used for documentation purposes as well.
4545
- Then, we define a potential error that this authenticator might raise. In this case, the API token provided may be invalid and we'll raise that error.
46-
- Finally, we define an action which will be invoked. This is responsible for setting the `request.identity` property or raising an error if authentication has failed. You don't **have** to set a `request.identity` if you don't wish (anonymous access?) but unless you raise an error in here the endpoint execution will continue.
46+
- Finally, we define the `call` method which will be invoked when the authenticator is used. This is responsible for setting the `request.identity` property or raising an error if authentication has failed. You don't **have** to set a `request.identity` if you don't wish (anonymous access?) but unless you raise an error in here the endpoint execution will continue.
4747

4848
## Applying to the API
4949

@@ -55,7 +55,7 @@ module CoreAPI
5555

5656
authenticator Authenticator
5757

58-
# ... other API bits like controllers
58+
# ... routes, scopes etc...
5959

6060
end
6161
end

doc/consuming.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This document outlines the conventions used for consuming APIs built with Rapid.
44

55
## Requests
66

7-
The address of each API endpoint is made up from two parts - a controller name and an endpoint name. For example, a list of users would have a controller name of `users` and an endpoint name of `list`. This is sent to the API in the URL of the request. For example:
7+
Consumers can make requests to any resource provided by the API (and listed as a route).
88

99
```
1010
/api/v1/users/list

doc/endpoints.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ Endpoints are the main core of the framework and this is where the majority of t
1616
This is an example endpoint for reference purposes.
1717

1818
```ruby
19-
endpoint :update do
19+
class UpdateEndpoint < Rapid::Endpoint
20+
2021
# Defines some details about this endpoint for documentation
2122
name 'Update user details'
2223
description 'Updates a user details with the new information'
@@ -41,10 +42,11 @@ endpoint :update do
4142
potential_error Errors::ValidationError
4243

4344
# Defines the action that will run
44-
action do
45+
def call
4546
user = request.arguments[:user].resolve
4647
user.update!(request.arguments[:details].to_hash)
4748
response.add_field :user, user
4849
end
50+
4951
end
5052
```

doc/errors.md

+19-6
Original file line numberDiff line numberDiff line change
@@ -34,27 +34,34 @@ Errors can be raised by calling `raise_error` and providing either the name of t
3434
This example shows how to raise a class-defined error. You should also provide any fields that are required for the error as shown below with `errors`.
3535

3636
```ruby
37-
endpoint :create do
37+
class ExampleEndpoint < Rapid::Endpoint
38+
3839
potential_error Errors::ValidationError
39-
action do
40+
41+
def call
42+
# ...
4043
unless user.save
4144
raise_error Errors::ValidationError, errors: user.errors.full_messages
4245
end
4346
end
47+
4448
end
4549
```
4650

4751
This example shows how to raise an inline error.
4852

4953
```ruby
50-
endpoint :example do
54+
class ExampleEndpoint < Rapid::Endpoint
55+
5156
potential_error 'NotPermitted' do
5257
code :not_permitted
5358
http_status 406
5459
end
55-
action do
60+
61+
def call
5662
raise_error 'NotPermitted'
5763
end
64+
5865
end
5966
```
6067

@@ -64,6 +71,7 @@ If any exception occurs during your application lifecycle, it will be returned t
6471

6572
```ruby
6673
class ValidationError < Rapid::Error
74+
6775
# We can define things as normal for the error...
6876
code :validation_error
6977
http_status 416
@@ -83,15 +91,20 @@ end
8391
This will only be caught if the `ValidationError` error has been specified as a potential error for the endpoint where the exception is raised. So, you need to make sure to specify these if you want exceptions to be caught automatically. An endpoint might look this like:
8492

8593
```ruby
86-
endpoint :create do
94+
class ExampleEndpoint < Rapid::Endpoint
95+
8796
argument :user_id, :integer, required: true
8897
argument :properties, ArgumentSets::UserProperties, required: true
98+
8999
field :user, Objects::User
100+
90101
potential_error ValidationError
91-
action do
102+
103+
def call
92104
user = User.find(request.arguments[:user_id])
93105
user.update!(request.arguments[:properties]) # Might raise the error here but will be caught
94106
response.add_field :user, user
95107
end
108+
96109
end
97110
```

0 commit comments

Comments
 (0)