How to convert JSON payload with reserved keyword as name to record in Ballerina - type-conversion

I'm working on a Ballerina service that receives an event and converts the JSON payload to a record using the convert function. The event contains a field with the name type which is a reserved keyword in Ballerina. I can't change the payload of the event. Following is a simplified example code that doesn't compile, because of the string type; in the Event record. Changing type to Type or eventType allows compilation, but the execution throws errors because the field names of the JSON payload don't match the field names of the record.
import ballerina/http;
import ballerina/io;
type Event record {
string id;
string type;
string time;
};
#http:ServiceConfig { basePath: "/" }
service eventservice on new http:Listener(8080) {
#http:ResourceConfig { methods: ["POST"], path: "/" }
resource function handleEvent(http:Caller caller, http:Request request) {
json|error payload = request.getJsonPayload();
Event|error event = Event.convert(payload);
io:println(event);
http:Response response = new;
_ = caller -> respond(response);
}
}
Here is a curl command that sends an example event with a JSON payload and a field with the name type.
curl -X POST localhost:8080 -H "content-type: application/json" -d "{\"id\":\"1\",\"type\":\"newItem\",\"time\":\"now\"}"
I read through Ballerinas API documentation and found nothing on this topic.
Coming from the Java world, I would expect something like an annotation on the records field like this:
type Event record {
string id;
#JsonProperty("type")
string eventType;
string time;
};
Has anyone run into this problem and even better found a solution?

You can define the Event as follows:
type Event record {
string id;
string 'type;
string time;
};
The ' is used to escape the keyword in Ballerina.
When accessing it also you can use it as event.'type
Here you can find an example usage.

Related

How to convert SinkRecord to JSON string?

Imagine myAPICreate requires a JSON string.
public void put(Collection<SinkRecord> collection) {
for (SinkRecord record : collection) {
JSONObject recordJson = toJSON(record.value());
String recordJsonString = recordJson.toString();
myAPICreate(recordJsonString);
}
}
toJSON is a helper I have defined which just takes the record and returns a JSONObject.
JSONObject json = new JSONObject()
.put("a", record.getString("a"))
.put("b", record.getString("b"))
.put("c", record.getString("c"));
I feel like I might be doing a lot of redundant work here. Is it necessary to have the code in put convert it to JSON or is there a way to use the converters so that record already comes in as JSON or a JSON string? Then I can just pass myAPICreate(record.value().toString()) without having to manually do it?
When you create a SinkRecord, you have a key & value schema w/ a key and value Object. Those objects should be Struct instances that must be created with the matching Schema
In the Connector configuration, you would then use JSONConverter (or other converter) to get the serialized output

call two different rest methods with same URI

I have two Rest URIs :
// URI n1 : GET /users/{userName}
public ResponseEntity<userDto> findUserByName(
#PathVariable( value = "userName", required = true)
String userName
);
// URI n2 : GET /users/{userID}
public ResponseEntity<userDto> findUserByID(
#PathVariable( value = "userID", required = true)
Long userID
);
When I call GET /users/SuperUser123 I want the first function to respond and when I call GET /users/1854 I want the second one respond. What really happens is that the first function is always called for both cases (as the param is always of type String).
So how can I achieve what I want while respecting REST API URI recommendations ?
It will give ambiguous mapping runtime exception as the url pattern is same for both the methods.
If your url has some pattern like starting for superuser or something then you can use regex patterns to make it work.
In below example first method method will get called if the path variable is a digit otherwise second method for alphabets.you can change regex pattern accordingly.
#RequestMapping("{id:[0-9]+}")
public String handleRequest(#PathVariable("id") String userId, Model model){
model.addAttribute("msg", "profile id: "+userId);
return "my-page";
}
#RequestMapping("{name:[a-zA-Z]+}")
public String handleRequest2 (#PathVariable("name") String deptName, Model model) {
model.addAttribute("msg", "dept name : " + deptName);
return "my-page";
}

spring cloud stream kafka 2.0 - StreamListener with condition

I'm trying to create a consumer using StreamListener annotation and condition attirbute.However , i'm getting the following exception :
org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Integer] for value 'test'; nested exception is java.lang.NumberFormatException: For input string: "test"
TestListener:
#StreamListener(target=ITestSink.CHANNEL_NAME,condition="payload['test'] == 'test'")
public void test(#Payload TestObj message) {
log.info("message is {}",message.getName());
}
TestObj:
#Data
#ToString(callSuper=true)
public class TestObj {
#JsonProperty("test")
private String test;
#JsonProperty("name")
private String name;
}
can someone assist with this issue?
the payload of the message is not yet converted from the wire format (byte[]) to the desired type. In other words, it has not yet gone through the type conversion process described in the Content Type Negotiation.
So, unless you use a SPeL expression that evaluates raw data (for example, the value of the first byte in the byte array), use message header-based expressions (such as condition = "headers['type']=='dog'").
Example:
#StreamListener(target = Sink.INPUT, condition = "headers['type']=='bogey'")
public void receiveBogey(#Payload BogeyPojo bogeyPojo) {
// handle the message
}
Check the Spring documentation here.
From what you show, it should work. I suggest you remove the condition, then set breakpoint to debug. Then you should be able to know which actual type is.

How to handle optional query parameters in Play framework

Lets say I have an already functioning Play 2.0 framework based application in Scala that serves a URL such as:
http://localhost:9000/birthdays
which responds with a listing of all known birthdays
I now want to enhance this by adding the ability to restrict results with optional "from" (date) and "to" request params such as
http://localhost:9000/birthdays?from=20120131&to=20120229
(dates here interpreted as yyyyMMdd)
My question is how to handle the request param binding and interpretation in Play 2.0 with Scala, especially given that both of these params should be optional.
Should these parameters be somehow expressed in the "routes" specification? Alternatively, should the responding Controller method pick apart the params from the request object somehow? Is there another way to do this?
Encode your optional parameters as Option[String] (or Option[java.util.Date], but you’ll have to implement your own QueryStringBindable[Date]):
def birthdays(from: Option[String], to: Option[String]) = Action {
// …
}
And declare the following route:
GET /birthday controllers.Application.birthday(from: Option[String], to: Option[String])
A maybe less clean way of doing this for java users is setting defaults:
GET /users controllers.Application.users(max:java.lang.Integer ?= 50, page:java.lang.Integer ?= 0)
And in the controller
public static Result users(Integer max, Integer page) {...}
One more problem, you'll have to repeat the defaults whenever you link to your page in the template
#routes.Application.users(max = 50, page = 0)
In Addition to Julien's answer. If you don't want to include it in the routes file.
You can get this attribute in the controller method using RequestHeader
String from = request().getQueryString("from");
String to = request().getQueryString("to");
This will give you the desired request parameters, plus keep your routes file clean.
Here's Julien's example rewritten in java, using F.Option: (works as of play 2.1)
import play.libs.F.Option;
public static Result birthdays(Option<String> from, Option<String> to) {
// …
}
Route:
GET /birthday controllers.Application.birthday(from: play.libs.F.Option[String], to: play.libs.F.Option[String])
You can also just pick arbitrary query parameters out as strings (you have to do the type conversion yourself):
public static Result birthdays(Option<String> from, Option<String> to) {
String blarg = request().getQueryString("blarg"); // null if not in URL
// …
}
For optional Query parameters, you can do it this way
In routes file, declare API
GET /birthdays controllers.Application.method(from: Long, to: Long)
You can also give some default value, in case API doesn't contain these query params it will automatically assign the default values to these params
GET /birthdays controllers.Application.method(from: Long ?= 0, to: Long ?= 10)
In method written inside controller Application these params will have value null if no default values assigned else default values.
My way of doing this involves using a custom QueryStringBindable. This way I express parameters in routes as:
GET /birthdays/ controllers.Birthdays.getBirthdays(period: util.Period)
The code for Period looks like this.
public class Period implements QueryStringBindable<Period> {
public static final String PATTERN = "dd.MM.yyyy";
public Date start;
public Date end;
#Override
public F.Option<Period> bind(String key, Map<String, String[]> data) {
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN);
try {
start = data.containsKey("startDate")?sdf.parse(data.get("startDate") [0]):null;
end = data.containsKey("endDate")?sdf.parse(data.get("endDate")[0]):null;
} catch (ParseException ignored) {
return F.Option.None();
}
return F.Option.Some(this);
}
#Override
public String unbind(String key) {
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN);
return "startDate=" + sdf.format(start) + "&" + "endDate=" + sdf.format(end);
}
#Override
public String javascriptUnbind() {
return null;
}
public void applyDateFilter(ExpressionList el) {
if (this.start != null)
el.ge("eventDate", this.start);
if (this.end != null)
el.le("eventDate", new DateTime(this.end.getTime()).plusDays(1).toDate());
}
}
applyDateFilter is just a convienence method i use in my controllers if I want to apply date filtering to the query. Obviously you could use other date defaults here, or use some other default than null for start and end date in the bind method.

Return raw string from REST service method

I have a REST service method written in C#, defined as below:
[WebGet(UriTemplate = "/{par1}/{par2}/{par3}")]
public string ProcessGet(string par1, string par2, string par3)
{
return Execute(...);
}
It should return result as XML or JSON, based on one parameter (I generate the json and XML serialization)
How can I make this method to return the RAW string, just as I created it, without HTMLEncoding it?
Thank you
Return it as a Stream - that causes the "raw" mode to be used and WCF will not touch your response. You can find more information at http://blogs.msdn.com/b/carlosfigueira/archive/2008/04/17/wcf-raw-programming-model-web.aspx.