How can I get Quarkus Swagger to recognize JsonSerialize(as=), JsonDeserialize(as=) - swagger

I'm using Quarkus 1.10.3.Final (including quarkus-resteasy-jackson and quarkus-smallrye-openapi) and the Immutables library (org.immmutables:value:2.8.8) to define various application models. My problem is the Swagger UI doesn't seem to recognize the standard jackson #JsonSerialize(as=) and #JsonDeserialize(as=) annotations.
In the model example defined below, I expect to see four (4) fields in the components section of the Swagger UI page.
Model Example
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonDeserialize(as = ImmutableFieldMetaData.class)
#JsonSerialize(as = ImmutableFieldMetaData.class)
#Value.Immutable
public interface FieldMetaData {
String field();
String type();
Boolean deprecated();
String description();
}
When looking at the Swagger UI page, however, the resulting component shows up as an empty object:
This issue in Swagger UI was addressed in version 1.5.14 of Swagger UI, then subsequently dropped as of Swagger UI 2.0, then fixed again in 2.1.
After all that lead-up, my question is simple:
Short of abandoning Immutables, does anyone know of a work around?

After diving into the source code of smallrye-openapi, you can add the #Schema annotation to each Immutables field, i.e.
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonDeserialize(as = ImmutableFieldMetaData.class)
#JsonSerialize(as = ImmutableFieldMetaData.class)
#Value.Immutable
#Schema(implementation = ImmutableFieldMetaData.class)
public interface FieldMetaData {
String field();
String type();
Boolean deprecated();
String description();
}
You can also just annotate the fields as #Schema if desired.

Related

ASP.NET Boilerplate: Constraining user Input DTO property value to specific set of values

I've recently started using ASP.NET Boilerplate as a starting point for a project I'm working on - Asp.NET Core Web Api + Angular, and I'm having trouble finding some information regarding something that seems to me like a common use issue. I'll try to explain what I mean the best I can.
What I want to do is to have an API endpoint that takes a parameter that needs to be restricted to a specific set of values - so, I wanted to use Enum for this purpose.
This all works nice as far as parameter validation goes, but I'm confused about how to make this work well with swagger and shared proxy generated services for fontend by Nswag.
Enum parameter is displayed as integer in the documentation parameter example, so it's not clear just by looking at the swagger documentation what is the set of available values, because int values have no meaning to the user that is looking at the documentation.
Lets use an example like this, user input used in one of app service methods:
Service method:
public async Task DoSomething(SomethingInput input)
{
//some code handling user input
}
Input DTO:
public class SomethingInput : EntityDto<string>
{
[EnumDataType(typeof(SomethingEnum))]
public SomethingEnum Something { get; set; }
public int SomeOtherData { get; set; }
}
Enum:
public enum SomethingEnum
{
Option1,
Option2,
Option3
}
In this case, available values for SomethingEnum in SomethingInput will be shown as 0, 1, 3, instead of "Option1", "Option2", "Option2".
I tried using DescribeAllEnumsAsStrings for swagger generation options which helps with the documentation, but it creates an issue in the Angular project functionality - to be specific, changing tenant fails because of the way enums are generated using DescribeAllEnumsAsStrings by Nswag tool - it expects values to be integers, rather than strings.
export enum IsTenantAvailableOutputState {
Available = <any>"Available",
InActive = <any>"InActive",
NotFound = <any>"NotFound",
}
Response value returned by API is numeric, and this code doesn't know how to parse the value correctly anymore because enum no longer has integer values:
export class AppTenantAvailabilityState {
static Available: number = IsTenantAvailableOutputState._1;
static InActive: number = IsTenantAvailableOutputState._2;
static NotFound: number = IsTenantAvailableOutputState._3;
}
The question here is, what is the best practice for having these kind of restrained parameters in routes or DTO objects used as user inputs, so that they are correctly displayed in swagger doc, and do not break down anything in angular project when regenerating shared proxies with Nswag tool?
I believe that instead of DescribeAllEnumsAsStrings() you need to add the StringEnumConverter to the SerializerSettings. In other words in ConfigureServices() in Startup.cs you should have something like this:
services
.AddMvc(options => options.Filters.Add(new CorsAuthorizationFilterFactory(_defaultCorsPolicyName)))
.AddJsonOptions(options => options.SerializerSettings.Converters.Add(new StringEnumConverter()));
my english is not very good but I will try to be clear.
I Had the same problem someday I have a Enum for gender and I Wanted to display
Male and Female instead of 0 and 1 what is NSwag generates, to solve this In Angular template I made a Array in a static class that is like a copy of backend enum, and with this was able to load a Select.
Examples here
Enum in backend
public enum Sexo
{
[Description("Masculino")]
M,
[Description("Fenemino")]
F
}
Enum in angular after Nswag generates it.
export enum EstudianteDtoSexo {
_0 = 0,
_1 = 1,
}
The array in angular(That was the way for me to solve the problem)
export class SexoArray {
static Sexo = [
{ value: 0, name: 'Masculino' },
{ value: 1, name: 'Femenino'}
];
Then I fill a ng-select with the elements of SexoArray bind it like this
<div [ngClass]="(nombres.invalid && nombres.touched)?' form-group mb-4 has-error ':' form-group mb-4'">
<label class="col-form-label">{{l("Sexo")}}<span class="text-danger"> *</span> </label>
<ng-select
[(ngModel)]="estudiante.sexo"
name= "sexoSelect"
#sexoModel = "ngModel"
required>
<ng-option *ngFor="let sex of sexo" [value]="sex.value">{{sex.name}}</ng-option>
</ng-select>
<app-input-validation [input]="sexoModel"></app-input-validation>
</div>
In this example estudiante.sexo is of type EstudianteDtoSexo
I Hope this could help you.

Dataflow output parameterized type to avro file

I have a pipeline that successfully outputs an Avro file as follows:
#DefaultCoder(AvroCoder.class)
class MyOutput_T_S {
T foo;
S bar;
Boolean baz;
public MyOutput_T_S() {}
}
#DefaultCoder(AvroCoder.class)
class T {
String id;
public T() {}
}
#DefaultCoder(AvroCoder.class)
class S {
String id;
public S() {}
}
...
PCollection<MyOutput_T_S> output = input.apply(myTransform);
output.apply(AvroIO.Write.to("/out").withSchema(MyOutput_T_S.class));
How can I reproduce this exact behavior except with a parameterized output MyOutput<T, S> (where T and S are both Avro code-able using reflection).
The main issue is that Avro reflection doesn't work for parameterized types. So based on these responses:
Setting Custom Coders & Handling Parameterized types
Using Avrocoder for Custom Types with Generics
1) I think I need to write a custom CoderFactory but, I am having difficulty figuring out exactly how this works (I'm having trouble finding examples). Oddly enough, a completely naive coder factory appears to let me run the pipeline and inspect proper output using DataflowAssert:
cr.RegisterCoder(MyOutput.class, new CoderFactory() {
#Override
public Coder<?> create(List<? excents Coder<?>> componentCoders) {
Schema schema = new Schema.Parser().parse("{\"type\":\"record\,"
+ "\"name\":\"MyOutput\","
+ "\"namespace\":\"mypackage"\","
+ "\"fields\":[]}"
return AvroCoder.of(MyOutput.class, schema);
}
#Override
public List<Object> getInstanceComponents(Object value) {
MyOutput<Object, Object> myOutput = (MyOutput<Object, Object>) value;
List components = new ArrayList();
return components;
}
While I can successfully assert against the output now, I expect this will not cut it for writing to a file. I haven't figured out how I'm supposed to use the provided componentCoders to generate the correct schema and if I try to just shove the schema of T or S into fields I get:
java.lang.IllegalArgumentException: Unable to get field id from class null
2) Assuming I figure out how to encode MyOutput. What do I pass to AvroIO.Write.withSchema? If I pass either MyOutput.class or the schema I get type mismatch errors.
I think there are two questions (correct me if I am wrong):
How do I enable the coder registry to provide coders for various parameterizations of MyOutput<T, S>?
How do I values of MyOutput<T, S> to a file using AvroIO.Write.
The first question is to be solved by registering a CoderFactory as in the linked question you found.
Your naive coder is probably allowing you to run the pipeline without issues because serialization is being optimized away. Certainly an Avro schema with no fields will result in those fields being dropped in a serialization+deserialization round trip.
But assuming you fill in the schema with the fields, your approach to CoderFactory#create looks right. I don't know the exact cause of the message java.lang.IllegalArgumentException: Unable to get field id from class null, but the call to AvroCoder.of(MyOutput.class, schema) should work, for an appropriately assembled schema. If there is an issue with this, more details (such as the rest of the stack track) would be helpful.
However, your override of CoderFactory#getInstanceComponents should return a list of values, one per type parameter of MyOutput. Like so:
#Override
public List<Object> getInstanceComponents(Object value) {
MyOutput<Object, Object> myOutput = (MyOutput<Object, Object>) value;
return ImmutableList.of(myOutput.foo, myOutput.bar);
}
The second question can be answered using some of the same support code as the first, but otherwise is independent. AvroIO.Write.withSchema always explicitly uses the provided schema. It does use AvroCoder under the hood, but this is actually an implementation detail. Providing a compatible schema is all that is necessary - such a schema will have to be composed for each value of T and S for which you want to output MyOutput<T, S>.

How to override Grails' default (binding) conversion of parameter values with commas to String array?

I'm using Grails 2.3.7 and have a controller method which binds the request to a command class. One of the fields in the command class is a String array, as it expects multiple params with the same name to be specified - e.g. ?foo=1&foo=2&foo=3&bar=0.
This works fine most of the time. The one case where it fails is if the param value contains a comma - e.g. ?foo=val1,val2&foo=val3,val4. In this case I'm getting an array with 4 values: ["val1","val2","val3","val4"], not the intended output of ["val1,val2","val1,val2"]. URL escaping/encoding the comma does not help and in my cases the param value is surrounded by quote characters as well (foo=%22a+string+with+commas,+etc%22).
I had a similar problem with Spring 3.x which I was able to solve: How to prevent parameter binding from interpreting commas in Spring 3.0.5?. I've attempted one of the answers by adding the following method to my controller:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String[].class, new StringArrayPropertyEditor(null));
}
However this didn't work.
I also tried specifying a custom conversion service, based on the comment in https://jira.grails.org/browse/GRAILS-8997.
Config/spring/resources.groovy:
beans = {
conversionService (org.springframework.context.support.ConversionServiceFactoryBean) {
converters = [new CustomStringToArrayConverter()]
}
}
and
import org.springframework.core.convert.converter.Converter
import org.springframework.util.StringUtils
class CustomStringToArrayConverter implements Converter<String, String[]> {
#Override
public String[] convert(String source) {
return StringUtils.delimitedListToStringArray(source, ";");
}
}
but I couldn't get this to work either.
For now, I've come up with a work around. My Controller method has an extra line to set the troublesome field explicitly with:
commandObj.foo = params.list('foo') as String[]
I'm still open to suggestions on how to configure grails to not split on a comma...

Grails data binding - command objects with Lists

Grails 1.3.7
Trouble with data binding Command objects that have List content. Example Command:
class Tracker {
String name
String description
List<Unit> units = new ArrayList()
}
class Unit {
String name
Long unitMax
Long unitMin
}
create GSP for Tracker has the Unit fields. One example:
<g:textField name="units[0].unitMax" value=""/>
TrackerController save method:
def save = { Tracker trackerInstance ->
trackerInstance = trackingService.saveOrUpdateTracker(trackerInstance)
}
But, always java.lang.IndexOutOfBoundsException
Alternatively, if I update controller to:
def save = {
Tracker trackerInstance = new Tracker()
trackerInstance.properties = params
....
Then groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: properties for class: com.redbrickhealth.dto.Tracker
Any ideas?
There seems to be a difference between binding in GORM vs Command objects.
Maybe I need to extend and register a PropertyEditorSupport for Unit?
-Todd
Since Groovy 1.8.7 the List interface has a method called withLazyDefault that can be used instead of apache commons ListUtils:
List<Unit> units = [].withLazyDefault { new Unit() }
This creates a new Unit instance every time units is accessed with a non-existent index.
See the documentation of withLazyDefault for more details. I also wrote a small blog post about this a few days ago.
Grails requires an command with existing list, that will be filled with data from reques.
If you know exact number of units, say 3, you can:
class Tracker {
String name
String description
List<Unit> units = [new Unit(), new Unit(), new Unit()]
}
or use LazyList from apache commons collections
import org.apache.commons.collections.ListUtils
import org.apache.commons.collections.Factory
class Tracker {
String name
String description
List<Unit> units = ListUtils.lazyList([], {new Unit()} as Factory)
}

Biztalk mapping Date to String

I'm working on a biztalk project and use a map to create the new message.
Now i want to map a datefield to a string.
I thought i can do it on this way with an Function Script with inline C#
public string convertDateTime(DateTime param)
{
return System.Xml.XmlConvert.ToString(param,ÿyyyMMdd");
}
But this doesn't work and i receive an error. How can i do the convert in the map?
It's a Biztalk 2006 project.
Without the details of the error you are seeing it is hard to be sure but I'm quite sure that your map is failing because all the parameters within the BizTalk XSLT engine are passed as strings1.
When I try to run something like the function you provided as inline C# I get the following error:
Object of type 'System.String' cannot be converted to type 'System.DateTime'
Replace your inline C# with something like the following:
public string ConvertDateTime(string param1)
{
DateTime inputDate = DateTime.Parse(param1);
return inputDate.ToString("yyyyMMdd");
}
Note that the parameter type is now string, and you can then convert that to a DateTime and perform your string format.
As other answers have suggested, it may be better to put this helper method into an external class - that way you can get your code under test to deal with edge cases, and you also get some reuse.
1 The fact that all parameters in the BizTalk XSLT are strings can be the source of a lot of gotchas - one other common one is with math calculations. If you return numeric values from your scripting functoids BizTalk will helpfully convert them to strings to map them to the outbound schema but will not so helpfully perform some very random rounding on the resulting values. Converting the return values to strings yourself within the C# will remove this risk and give you the expected results.
If you're using the mapper, you just need a Scripting Functiod (yes, using inline C#) and you should be able to do:
public string convertDateTime(DateTime param)
{
return(param.ToString("YYYYMMdd");
}
As far as I know, you don't need to call the System.Xml namespace in anyway.
I'd suggest
public static string DateToString(DateTime dateValue)
{
return String.Format("{0:yyyyMMdd}", dateValue);
}
You could also create a external Lib which would provide more flexibility and reusability:
public static string DateToString(DateTime dateValue, string formatPicture)
{
string format = formatPicture;
if (IsNullOrEmptyString(formatPicture)
{
format = "{0:yyyyMMdd}";
}
return String.Format(format, dateValue);
}
public static string DateToString(DateTime dateValue)
{
return DateToString(dateValue, null);
}
I tend to move every function I use twice inside an inline script into an external lib. Iit will give you well tested code for all edge cases your data may provide because it's eays to create tests for these external lib functions whereas it's hard to do good testing on inline scripts in maps.
This blog will solve your problem.
http://biztalkorchestration.blogspot.in/2014/07/convert-datetime-format-to-string-in.html?view=sidebar
Regards,
AboorvaRaja
Bangalore
+918123339872
Given that maps in BizTalk are implemented as XSL stylesheets, when passing data into a msxsl scripting function, note that the data will be one of types in the Equivalent .NET Framework Class (Types) from this table here. You'll note that System.DateTime isn't on the list.
For parsing of xs:dateTimes, I've generally obtained the /text() node and then parse the parameter from System.String:
<CreateDate>
<xsl:value-of select="userCSharp:GetDateyyyyMMdd(string(s0:StatusIdChangeDate/text()))" />
</CreateDate>
And then the C# script
<msxsl:script language="C#" implements-prefix="userCSharp">
<![CDATA[
public System.String GetDateyyyyMMdd(System.String p_DateTime)
{
return System.DateTime.Parse(p_DateTime).ToString("yyyyMMdd");
}
]]>

Resources