Skip to content

Commit

Permalink
Merge pull request #1771 from ballerina-platform/query-name-support
Browse files Browse the repository at this point in the history
Add OpenAPI mapping support for query annotation with name field
  • Loading branch information
TharmiganK authored Sep 19, 2024
2 parents 742e11e + 9d88386 commit 8b7cbb3
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public final class Constants {
public static final String ERROR_PAYLOAD = "ErrorPayload";
public static final String TREAT_NILABLE_AS_OPTIONAL = "treatNilableAsOptional";
public static final String HTTP_HEADER = "http:Header";
public static final String HTTP_QUERY = "http:Query";
public static final String BYTE = "byte";
public static final String XML = "xml";
public static final String JSON = "json";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.Map;
import java.util.Objects;

import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getQueryName;
import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.unescapeIdentifier;

/**
Expand All @@ -62,7 +63,8 @@ public QueryParameterMapper(ParameterNode parameterNode, Map<String, String> api
Symbol parameterSymbol = additionalData.semanticModel().symbol(parameterNode).orElse(null);
if (Objects.nonNull(parameterSymbol) && (parameterSymbol instanceof ParameterSymbol queryParameter)) {
this.type = queryParameter.typeDescriptor();
this.name = unescapeIdentifier(queryParameter.getName().get());
String paramName = unescapeIdentifier(queryParameter.getName().get());
this.name = getQueryName(parameterNode, paramName);
this.isRequired = queryParameter.paramKind().equals(ParameterKind.REQUIRED);
this.description = apiDocs.get(queryParameter.getName().get());
this.treatNilableAsOptional = treatNilableAsOptional;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
import static io.ballerina.openapi.service.mapper.Constants.BALLERINA;
import static io.ballerina.openapi.service.mapper.Constants.EMPTY;
import static io.ballerina.openapi.service.mapper.Constants.HTTP;
import static io.ballerina.openapi.service.mapper.Constants.HTTP_HEADER;
import static io.ballerina.openapi.service.mapper.Constants.HTTP_QUERY;
import static io.ballerina.openapi.service.mapper.Constants.HTTP_SERVICE_CONTRACT;
import static io.ballerina.openapi.service.mapper.Constants.HYPHEN;
import static io.ballerina.openapi.service.mapper.Constants.JSON_EXTENSION;
Expand Down Expand Up @@ -439,6 +441,14 @@ public static String unescapeIdentifier(String parameterName) {
}

public static String getHeaderName(ParameterNode parameterNode, String defaultName) {
return getParamName(HTTP_HEADER, parameterNode, defaultName);
}

public static String getQueryName(ParameterNode parameterNode, String defaultName) {
return getParamName(HTTP_QUERY, parameterNode, defaultName);
}

public static String getParamName(String paramTypeName, ParameterNode parameterNode, String defaultName) {
NodeList<AnnotationNode> annotations = null;
if (parameterNode instanceof RequiredParameterNode requiredParameterNode) {
annotations = requiredParameterNode.annotations();
Expand All @@ -449,17 +459,17 @@ public static String getHeaderName(ParameterNode parameterNode, String defaultNa
return defaultName;
}
for (AnnotationNode annotation : annotations) {
if (annotation.annotReference().toString().trim().equals("http:Header")) {
String valueExpression = getNameFromHeaderAnnotation(annotation);
if (valueExpression != null) {
if (annotation.annotReference().toString().trim().equals(paramTypeName)) {
String valueExpression = getNameFromParamAnnotation(annotation);
if (Objects.nonNull(valueExpression)) {
return valueExpression;
}
}
}
return defaultName;
}

private static String getNameFromHeaderAnnotation(AnnotationNode annotation) {
private static String getNameFromParamAnnotation(AnnotationNode annotation) {
Optional<MappingConstructorExpressionNode> annotationRecord = annotation.annotValue();
if (annotationRecord.isEmpty()) {
return null;
Expand Down
43 changes: 42 additions & 1 deletion docs/ballerina-to-oas/spec/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,26 @@ parameters:
nullable: true
```

The query parameter name in the schema is defaulted to the Ballerina parameter name. But this can be overridden using the `name` attribute in the `@http:Query` annotation.

```ballerina
service /api on new http:Listener(9090) {
resource function get path(@http:Query{name: "userName"} string param) {
// ...
}
}
```

```yml
parameters:
- name: userName
in: query
required: true
schema:
type: string
```

If the query parameter type is a `map` or `record` with `anydata` fields, then the query parameter schema is wrapped with `content` and `application/json` to indicate that the query parameter should be a JSON object which should be encoded properly.

```ballerina
Expand Down Expand Up @@ -836,6 +856,27 @@ parameters:
nullable: true
```

The header parameter name in the schema is defaulted to the Ballerina parameter name. But this can be overridden using the `name` attribute in the `@http:Header` annotation.

```ballerina
service /api on new http:Listener(9090) {
resource function get path(@http:Header{name: "xApiKeys"} string[] param) {
// ...
}
}
```

```yml
parameters:
- name: xApiKeys
in: header
schema:
type: array
items:
type: string
```

Additionally, the header parameter can be a closed `record` which contains the above basic types as fields. In that case, each field of this record represents a header parameter.

```ballerina
Expand Down Expand Up @@ -1029,7 +1070,7 @@ additionalProperties: false
</td>
</tr>
<tr>
<td><code>string:Char<code></td>
<td><code>string:Char</code></td>
<td>

```yml
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ stdlibJwtVersion=2.13.0
stdlibOAuth2Version=2.12.0

# Stdlib Level 05
stdlibHttpVersion=2.12.0
stdlibHttpVersion=2.12.1-20240918-130700-9906dbe

# Stdlib Level 06
stdlibGrpcVersion=1.12.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ public void testQueryscenario09() throws IOException {
TestUtils.compareWithGeneratedFile(ballerinaFilePath, "query/query_scenario09.yaml");
}

@Test(description = "Query parameters configured with name field")
public void testQueryscenario10() throws IOException {
Path ballerinaFilePath = RES_DIR.resolve("query/query_scenario10.bal");
TestUtils.compareWithGeneratedFile(ballerinaFilePath, "query/query_scenario10.yaml");
}

@AfterMethod
public void cleanUp() {
TestUtils.deleteDirectory(this.tempDir);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
openapi: 3.0.1
info:
title: PayloadV
version: 0.0.0
servers:
- url: "{server}:{port}/payloadV"
variables:
server:
default: http://localhost
port:
default: "9090"
paths:
/query:
get:
operationId: getQuery
parameters:
- name: query0
in: query
schema:
type: string
default: ""
- name: q1
in: query
schema:
type: string
- name: query2
in: query
schema:
type: array
items:
type: string
default: []
- name: query3
in: query
schema:
type: array
items:
type: string
default:
- one
- two
- three
- name: query4
in: query
schema:
type: array
items:
type: integer
format: int64
default:
- 1
- 2
- 3
- name: query5
in: query
schema:
type: array
items:
type: number
format: float
default:
- 1
- 2.3
- 4.56
- name: query6
in: query
content:
application/json:
schema:
type: object
additionalProperties:
type: string
default:
name: John
city: London
- name: query7
in: query
content:
application/json:
schema:
type: array
items:
type: object
additionalProperties:
type: string
default:
- name: John
age: "25"
- name: David
age: "30"
- name: query8
in: query
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Record"
default:
name: John
address:
number: 14/7
streetName: 2nd cross street
city: London
- name: query9
in: query
content:
application/json:
schema:
type: object
additionalProperties:
type: number
format: float
default: {}
- name: query10
in: query
schema:
type: array
items:
type: boolean
default:
- true
- false
- true
responses:
"200":
description: Ok
content:
text/plain:
schema:
type: string
"400":
description: BadRequest
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorPayload"
components:
schemas:
ErrorPayload:
required:
- message
- method
- path
- reason
- status
- timestamp
type: object
properties:
timestamp:
type: string
status:
type: integer
format: int64
reason:
type: string
message:
type: string
path:
type: string
method:
type: string
Record:
required:
- address
- name
type: object
properties:
name:
type: string
address:
type: object
additionalProperties:
type: string
additionalProperties: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import ballerina/http;

listener http:Listener helloEp = new (9090);

type Record record {|
string name;
map<string> address;
|};

const query1 = "query1";

service /payloadV on helloEp {

resource function get query(
@http:Query{name: "query0"} string q0 = "",
@http:Query{name: query1} string q1 = string `"John"`,
@http:Query{name: "query2"} string[] q2 = [],
@http:Query{name: "query3"} string[] q3 = ["one", "two", "three"],
@http:Query{name: "query4"} int[] q4 = [1, 2, 3],
@http:Query{name: "query5"} float[] q5 = [1, 2.3, 4.56],
@http:Query{name: "query6"} map<string> q6 = {"name": "John", "city": "London"},
@http:Query{name: "query7"} map<string>[] q7 = [{"name": "John", age: "25"},
{name: "David", age: "30"}],
@http:Query{name: "query8" } Record q8 = {name: "John", address: {number: "14/7",
streetName: "2nd cross street", city: "London"}},
@http:Query{name: "query9"} map<float> q9 = {},
@http:Query{name: "query10"} boolean[] q10 = [true, false, true]
) returns string {
return "new";
}
}
Loading

0 comments on commit 8b7cbb3

Please sign in to comment.