Skip to content

Commit 847bd9f

Browse files
authored
Application: Use Rest Controller structure (#1145)
* REST API: Create separate Application controller * Remove "required" check * Simplify file docs * Account for updated type * Be less prescriptive of what an Icon can be Props @pfefferle * Update webfinger endpoint * Fix tests * Bring back some of the icon definition This reverts commit 0ee0142. This is meant to describe the endpoint response, not the Activitypub object
1 parent 8100153 commit 847bd9f

File tree

4 files changed

+261
-35
lines changed

4 files changed

+261
-35
lines changed

activitypub.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ function rest_init() {
4949
Rest\Collection::init();
5050
Rest\Interaction::init();
5151
Rest\Post::init();
52+
( new Rest\Application_Controller() )->register_routes();
5253
( new Rest\Webfinger_Controller() )->register_routes();
5354

5455
// Load NodeInfo endpoints only if blog is public.
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<?php
2+
/**
3+
* Application Controller file.
4+
*
5+
* @package Activitypub
6+
*/
7+
8+
namespace Activitypub\Rest;
9+
10+
use Activitypub\Model\Application;
11+
12+
/**
13+
* ActivityPub Application Controller.
14+
*/
15+
class Application_Controller extends \WP_REST_Controller {
16+
/**
17+
* The namespace of this controller's route.
18+
*
19+
* @var string
20+
*/
21+
protected $namespace = ACTIVITYPUB_REST_NAMESPACE;
22+
23+
/**
24+
* The base of this controller's route.
25+
*
26+
* @var string
27+
*/
28+
protected $rest_base = 'application';
29+
30+
/**
31+
* Register routes.
32+
*/
33+
public function register_routes() {
34+
\register_rest_route(
35+
$this->namespace,
36+
'/' . $this->rest_base,
37+
array(
38+
array(
39+
'methods' => \WP_REST_Server::READABLE,
40+
'callback' => array( $this, 'get_item' ),
41+
'permission_callback' => '__return_true',
42+
),
43+
'schema' => array( $this, 'get_item_schema' ),
44+
)
45+
);
46+
}
47+
48+
/**
49+
* Retrieves the application actor profile.
50+
*
51+
* @param \WP_REST_Request $request The request object.
52+
* @return \WP_REST_Response Response object.
53+
*/
54+
public function get_item( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
55+
$json = ( new Application() )->to_array();
56+
57+
$rest_response = new \WP_REST_Response( $json, 200 );
58+
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . \get_option( 'blog_charset' ) );
59+
60+
return $rest_response;
61+
}
62+
63+
/**
64+
* Retrieves the schema for the application endpoint.
65+
*
66+
* @return array Schema data.
67+
*/
68+
public function get_item_schema() {
69+
if ( $this->schema ) {
70+
return $this->add_additional_fields_schema( $this->schema );
71+
}
72+
73+
$this->schema = array(
74+
'$schema' => 'http://json-schema.org/draft-04/schema#',
75+
'title' => 'application',
76+
'type' => 'object',
77+
'properties' => array(
78+
'@context' => array(
79+
'type' => 'array',
80+
'items' => array(
81+
'type' => array( 'string', 'object' ),
82+
),
83+
),
84+
'id' => array(
85+
'type' => 'string',
86+
'format' => 'uri',
87+
),
88+
'type' => array(
89+
'type' => 'string',
90+
'enum' => array( 'Application' ),
91+
),
92+
'name' => array(
93+
'type' => 'string',
94+
),
95+
'icon' => array(
96+
'type' => 'object',
97+
'properties' => array(
98+
'type' => array(
99+
'type' => 'string',
100+
),
101+
'url' => array(
102+
'type' => 'string',
103+
'format' => 'uri',
104+
),
105+
),
106+
),
107+
'published' => array(
108+
'type' => 'string',
109+
'format' => 'date-time',
110+
),
111+
'summary' => array(
112+
'type' => 'string',
113+
),
114+
'url' => array(
115+
'type' => 'string',
116+
'format' => 'uri',
117+
),
118+
'inbox' => array(
119+
'type' => 'string',
120+
'format' => 'uri',
121+
),
122+
'outbox' => array(
123+
'type' => 'string',
124+
'format' => 'uri',
125+
),
126+
'streams' => array(
127+
'type' => 'array',
128+
'items' => array(
129+
'type' => 'string',
130+
),
131+
),
132+
'preferredUsername' => array(
133+
'type' => 'string',
134+
),
135+
'publicKey' => array(
136+
'type' => 'object',
137+
'properties' => array(
138+
'id' => array(
139+
'type' => 'string',
140+
'format' => 'uri',
141+
),
142+
'owner' => array(
143+
'type' => 'string',
144+
'format' => 'uri',
145+
),
146+
'publicKeyPem' => array(
147+
'type' => 'string',
148+
),
149+
),
150+
),
151+
'manuallyApprovesFollowers' => array(
152+
'type' => 'boolean',
153+
),
154+
'discoverable' => array(
155+
'type' => 'boolean',
156+
),
157+
'indexable' => array(
158+
'type' => 'boolean',
159+
),
160+
'webfinger' => array(
161+
'type' => 'string',
162+
),
163+
),
164+
);
165+
166+
return $this->add_additional_fields_schema( $this->schema );
167+
}
168+
}

includes/rest/class-server.php

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
use WP_REST_Server;
1212
use WP_REST_Response;
1313
use Activitypub\Signature;
14-
use Activitypub\Model\Application;
1514

1615
use function Activitypub\use_authorized_fetch;
1716

@@ -27,7 +26,6 @@ class Server {
2726
* Initialize the class, registering WordPress hooks.
2827
*/
2928
public static function init() {
30-
self::register_routes();
3129
self::add_hooks();
3230
}
3331

@@ -39,39 +37,6 @@ public static function add_hooks() {
3937
\add_filter( 'rest_request_parameter_order', array( self::class, 'request_parameter_order' ), 10, 2 );
4038
}
4139

42-
/**
43-
* Register routes
44-
*/
45-
public static function register_routes() {
46-
\register_rest_route(
47-
ACTIVITYPUB_REST_NAMESPACE,
48-
'/application',
49-
array(
50-
array(
51-
'methods' => \WP_REST_Server::READABLE,
52-
'callback' => array( self::class, 'application_actor' ),
53-
'permission_callback' => '__return_true',
54-
),
55-
)
56-
);
57-
}
58-
59-
/**
60-
* Render Application actor profile
61-
*
62-
* @return WP_REST_Response The JSON profile of the Application Actor.
63-
*/
64-
public static function application_actor() {
65-
$user = new Application();
66-
67-
$json = $user->to_array();
68-
69-
$rest_response = new WP_REST_Response( $json, 200 );
70-
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
71-
72-
return $rest_response;
73-
}
74-
7540
/**
7641
* Callback function to authorize an api request.
7742
*
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
/**
3+
* Application REST API endpoint test file.
4+
*
5+
* @package Activitypub
6+
*/
7+
8+
namespace Activitypub\Tests\Rest;
9+
10+
/**
11+
* Tests for Application REST API endpoint.
12+
*
13+
* @coversDefaultClass \Activitypub\Rest\Application_Controller
14+
*/
15+
class Test_Application_Controller extends \Activitypub\Tests\Test_REST_Controller_Testcase {
16+
17+
/**
18+
* Test route registration.
19+
*
20+
* @covers ::register_routes
21+
*/
22+
public function test_register_routes() {
23+
$routes = rest_get_server()->get_routes();
24+
$this->assertArrayHasKey( '/' . ACTIVITYPUB_REST_NAMESPACE . '/application', $routes );
25+
}
26+
27+
/**
28+
* Test schema.
29+
*
30+
* @covers ::get_item_schema
31+
*/
32+
public function test_get_item_schema() {
33+
$request = new \WP_REST_Request( 'OPTIONS', '/' . ACTIVITYPUB_REST_NAMESPACE . '/application' );
34+
$response = rest_get_server()->dispatch( $request )->get_data();
35+
36+
$this->assertArrayHasKey( 'schema', $response );
37+
$schema = $response['schema'];
38+
39+
// Test specific property types.
40+
$this->assertEquals( 'array', $schema['properties']['@context']['type'] );
41+
$this->assertEquals( 'string', $schema['properties']['id']['type'] );
42+
$this->assertEquals( 'uri', $schema['properties']['id']['format'] );
43+
$this->assertEquals( array( 'Application' ), $schema['properties']['type']['enum'] );
44+
$this->assertEquals( 'object', $schema['properties']['icon']['type'] );
45+
$this->assertEquals( 'date-time', $schema['properties']['published']['format'] );
46+
}
47+
48+
/**
49+
* Test get_item response.
50+
*
51+
* @covers ::get_item
52+
*/
53+
public function test_get_item() {
54+
$request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/application' );
55+
$response = rest_get_server()->dispatch( $request );
56+
57+
$this->assertEquals( 200, $response->get_status() );
58+
$this->assertStringContainsString( 'application/activity+json', $response->get_headers()['Content-Type'] );
59+
60+
$data = $response->get_data();
61+
62+
// Test required properties.
63+
$this->assertArrayHasKey( '@context', $data );
64+
$this->assertArrayHasKey( 'id', $data );
65+
$this->assertArrayHasKey( 'type', $data );
66+
$this->assertArrayHasKey( 'name', $data );
67+
$this->assertArrayHasKey( 'inbox', $data );
68+
$this->assertArrayHasKey( 'outbox', $data );
69+
70+
// Test property values.
71+
$this->assertEquals( 'Application', $data['type'] );
72+
$this->assertStringContainsString( '/activitypub/1.0/application', $data['id'] );
73+
$this->assertStringContainsString( '/activitypub/1.0/actors/-1/inbox', $data['inbox'] );
74+
$this->assertStringContainsString( '/activitypub/1.0/actors/-1/outbox', $data['outbox'] );
75+
}
76+
77+
/**
78+
* Test that the Application response matches its schema.
79+
*
80+
* @covers ::get_item
81+
* @covers ::get_item_schema
82+
*/
83+
public function test_response_matches_schema() {
84+
$request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/application' );
85+
$response = rest_get_server()->dispatch( $request );
86+
$data = $response->get_data();
87+
$schema = ( new \Activitypub\Rest\Application_Controller() )->get_item_schema();
88+
89+
$valid = \rest_validate_value_from_schema( $data, $schema );
90+
$this->assertNotWPError( $valid, 'Response failed schema validation: ' . ( \is_wp_error( $valid ) ? $valid->get_error_message() : '' ) );
91+
}
92+
}

0 commit comments

Comments
 (0)