Skip to content

Commit

Permalink
Merge pull request #2043 from ayeshLK/2201.9.x-dev
Browse files Browse the repository at this point in the history
[2201.9.x] Generate and host SwaggerUI for the generated OpenAPI specification as a built-in resource
  • Loading branch information
TharmiganK authored Jun 14, 2024
2 parents d9d00e9 + 1c11efc commit 5a8996d
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,14 @@ function testIntrospectionResourceLink() returns error? {
http:Response response = check httpIntroResTestClient->options("/hello");
test:assertEquals(response.statusCode, 204, msg = "Found unexpected statusCode");
test:assertEquals(check response.getHeader(common:ALLOW), "GET, OPTIONS", msg = "Found unexpected Header");
common:assertHeaderValue(check response.getHeader(common:LINK), "</hello/openapi-doc-dygixywsw>;rel=\"service-desc\"");
common:assertHeaderValue(check response.getHeader(common:LINK),
"</hello/openapi-doc-dygixywsw>;rel=\"service-desc\", </hello/swagger-ui-dygixywsw>;rel=\"swagger-ui\"");

response = check httpIntroResTestClient->options("/hello/greeting");
test:assertEquals(response.statusCode, 204, msg = "Found unexpected statusCode");
test:assertEquals(check response.getHeader(common:ALLOW), "GET, OPTIONS", msg = "Found unexpected Header");
common:assertHeaderValue(check response.getHeader(common:LINK), "</hello/openapi-doc-dygixywsw>;rel=\"service-desc\"");
common:assertHeaderValue(check response.getHeader(common:LINK),
"</hello/openapi-doc-dygixywsw>;rel=\"service-desc\", </hello/swagger-ui-dygixywsw>;rel=\"swagger-ui\"");
}

@test:Config {}
Expand Down
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added
- [Generate and host SwaggerUI for the generated OpenAPI specification as a built-in resource](https://github.com/ballerina-platform/ballerina-library/issues/6622)

### Fixed

- [Remove the resource level annotation restrictions](https://github.com/ballerina-platform/ballerina-library/issues/5831)
Expand Down
144 changes: 73 additions & 71 deletions docs/spec/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
_Owners_: @shafreenAnfar @TharmiganK @ayeshLK @chamil321
_Reviewers_: @shafreenAnfar @bhashinee @TharmiganK @ldclakmal
_Created_: 2021/12/23
_Updated_: 2023/04/17
_Updated_: 2024/06/13
_Edition_: Swan Lake


Expand Down Expand Up @@ -42,7 +42,9 @@ The conforming implementation of the specification is released and included in t
* 2.3.5.1. [Status Code Response](#2351-status-code-response)
* 2.3.5.2. [Return nil](#2352-return-nil)
* 2.3.5.3. [Default response status codes](#2353-default-response-status-codes)
* 2.3.6. [Introspection resource](#236-introspection-resource)
* 2.3.6. [OpenAPI specification resources](#236-openapi-specification-resources)
* 2.3.6.1. [Introspection resource](#2361-introspection-resource)
* 2.3.6.2. [SwaggerUI resource](#2362-swaggerui-resource)
* 2.4. [Client](#24-client)
* 2.4.1. [Client types](#241-client-types)
* 2.4.1.1. [Security](#2411-security)
Expand Down Expand Up @@ -944,116 +946,116 @@ response when returning `anydata` directly from a resource method.
| HEAD | Retrieve headers | 200 OK |
| OPTIONS | Retrieve permitted communication options | 200 OK |

#### 2.3.6. Introspection resource
#### 2.3.6. OpenAPI specification resources

The introspection resource is internally generated for each service and host the openAPI doc can be generated
(or retrieved) at runtime when requested from the hosted service itself. In order to get the openAPI doc hosted
resource path, user can send an OPTIONS request either to one of the resources or the service. The link header
in the 204 response specifies the location. Then user can send a GET request to the dynamically generated URL in the
link header with the relation openapi to get the openAPI definition for the service.
OAS resources are internally generated for each service and host the generated OpenAPI specification for the service in
different formats. In order to access these resources user can send an OPTIONS request either to one of the resources or
the service base-path. The link header in the 204 response specifies the location for the OAS resources.

Sample service
```ballerina
import ballerina/http;
import ballerina/openapi;
@openapi:ServiceInfo {
embed: true
}
service /hello on new http:Listener(9090) {
resource function get greeting() returns string {
return "Hello world";
}
}
```

Output of OPTIONS call to usual resource
Output of OPTIONS call to service base path
```ballerina
curl -v localhost:9090/hello/greeting -X OPTIONS
* Trying ::1...
curl -v localhost:9090/hello -X OPTIONS
* Trying 127.0.0.1:9090...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> OPTIONS /hello/greeting HTTP/1.1
* Connected to localhost (127.0.0.1) port 9090 (#0)
> OPTIONS /hello HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.64.1
> User-Agent: curl/7.68.0
> Accept: */*
>
>
< HTTP/1.1 204 No Content
< allow: GET, OPTIONS
< link: </hello/openapi-doc-dygixywsw>;rel="service-desc"
< server: ballerina/2.0.0-beta.2.1
< date: Wed, 18 Aug 2021 14:09:40 +0530
< link: </hello/openapi-doc-dygixywsw>;rel="service-desc", </hello/swagger-ui-dygixywsw>;rel="swagger-ui"
< server: ballerina
< date: Thu, 13 Jun 2024 20:04:11 +0530
<
* Connection #0 to host localhost left intact
* Closing connection 0
```

##### 2.3.6.1. Introspection resource

The introspection resource is one of the generated OAS resources, and it hosts the OpenAPI specification for the service
in JSON format. The user can send a GET request to the resource path specified in the link header with the relation
attribute set to `service-desc`.

Output of GET call to introspection resource
```ballerina
curl -v localhost:9090/hello/openapi-doc-dygixywsw
* Trying ::1...
* Trying 127.0.0.1:9090...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET /hello/openapi-doc-dygixywsw HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.64.1
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: application/json
< content-length: 634
< server: ballerina/2.0.0-beta.2.1
< date: Wed, 18 Aug 2021 14:22:29 +0530
< content-length: 675
< server: ballerina
< date: Thu, 13 Jun 2024 20:05:03 +0530
<
{
"openapi": "3.0.1",
"info": {
"title": " hello",
"version": "1.0.0"
"openapi" : "3.0.1",
"info" : {
"title" : "Hello",
"version" : "0.1.0"
},
"servers": [
{
"url": "localhost:9090/hello"
}
],
"paths": {
"/greeting": {
"get": {
"operationId": "operation1_get_/greeting",
"responses": {
"200": {
"description": "Ok",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
"servers" : [ {
"url" : "{server}:{port}/hello",
"variables" : {
"server" : {
"default" : "http://localhost"
},
"port" : {
"default" : "9090"
}
}
} ],
"paths" : {
"/greeting" : {
"get" : {
"operationId" : "getGreeting",
"responses" : {
"200" : {
"description" : "Ok",
"content" : {
"text/plain" : {
"schema" : {
"type" : "string"
}
}
}
}
}
}
}
},
"components": {}
}
}
}
}
```

Output of OPTIONS call to service base path
##### 2.3.6.2. SwaggerUI resource

```ballerina
curl -v localhost:9090/hello -X OPTIONS
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> OPTIONS /hello HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 204 No Content
< allow: GET, OPTIONS
< link: </hello/openapi-doc-dygixywsw>;rel="service-desc"
< server: ballerina/2.0.0-beta.2.1
< date: Thu, 19 Aug 2021 13:47:29 +0530
<
* Connection #0 to host localhost left intact
* Closing connection 0
```
The swagger-ui resource is one of the generated OAS resources, and it hosts the OpenAPI specification for the service in
HTML format. The user can view it in a web browser by accessing the URL specified in the HTTP link header, which has
relation attribute set to `swagger-ui`.

### 2.4. Client
A client allows the program to send network messages to a remote process according to the HTTP protocol. The fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,53 +18,40 @@

package io.ballerina.stdlib.http.api;

import java.util.List;

import static io.ballerina.stdlib.http.api.HttpConstants.SINGLE_SLASH;
import io.netty.handler.codec.http.HttpHeaderValues;

/**
* {@code HttpIntrospectionResource} is the resource which respond the service Open API JSON specification.
*
* @since SL beta 3
*/
public class HttpIntrospectionResource extends HttpResource {
public class HttpIntrospectionResource extends HttpOASResource {

private static final String RESOURCE_NAME = "openapi-doc-dygixywsw";
private static final String RESOURCE_METHOD = "$get$";
private static final String REL_PARAM = "rel=\"service-desc\"";
private final byte[] payload;

protected HttpIntrospectionResource(HttpService httpService, byte[] payload) {
String path = (httpService.getBasePath() + SINGLE_SLASH + RESOURCE_NAME).replaceAll("/+", SINGLE_SLASH);
httpService.setIntrospectionResourcePathHeaderValue("<" + path + ">;" + REL_PARAM);
this.payload = payload.clone();
}

public String getName() {
return RESOURCE_METHOD + RESOURCE_NAME;
}

public String getPath() {
return SINGLE_SLASH + RESOURCE_NAME;
public HttpIntrospectionResource(HttpService httpService, byte[] payload) {
super(httpService, REL_PARAM, RESOURCE_NAME);
this.payload = payload;
}

@Override
public byte[] getPayload() {
return this.payload.clone();
}

public List<String> getMethods() {
return List.of(HttpConstants.HTTP_METHOD_GET);
}

public List<String> getConsumes() {
return null;
@Override
protected String getResourceName() {
return RESOURCE_NAME;
}

public List<String> getProduces() {
return null;
@Override
public String getContentType() {
return HttpHeaderValues.APPLICATION_JSON.toString();
}

public static String getIntrospectionResourceId() {
return RESOURCE_METHOD + RESOURCE_NAME;
public static String getResourceId() {
return String.format("%s%s", RESOURCE_METHOD, RESOURCE_NAME);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package io.ballerina.stdlib.http.api;

import java.util.Collections;
import java.util.List;

import static io.ballerina.stdlib.http.api.HttpConstants.SINGLE_SLASH;

/**
* {@code HttpOASResource} is the super class for the service introspection resources.
*
* @since v2.11.2
*/
public abstract class HttpOASResource extends HttpResource {
protected static final String RESOURCE_METHOD = "$get$";

protected HttpOASResource(HttpService httpService, String rel, String resourcePath) {
String path = (httpService.getBasePath() + SINGLE_SLASH + resourcePath).replaceAll("/+", SINGLE_SLASH);
httpService.addOasResourceLink("<" + path + ">;" + rel);
}

@Override
public String getName() {
return String.format("%s%s", RESOURCE_METHOD, getResourceName());
}

@Override
public String getPath() {
return String.format("%s%s", SINGLE_SLASH, getResourceName());
}

@Override
public List<String> getMethods() {
return List.of(HttpConstants.HTTP_METHOD_GET);
}

@Override
public List<String> getConsumes() {
return Collections.emptyList();
}

@Override
public List<String> getProduces() {
return Collections.emptyList();
}

protected abstract String getResourceName();

public abstract byte[] getPayload();

public abstract String getContentType();
}
Loading

0 comments on commit 5a8996d

Please sign in to comment.