I was trying to make an example with ksqlDB
Based on two properties, in this case "sensor_id" and "user_id".
I calculated the variation of the "value" field, when both attributes match.
// TOPIC KAFKA
{"timestamp": 1000000000, "user_id": "AAAAA", "sensor_id": "sensor_1111", "value": 10000}
{"timestamp": 1555550000, "user_id": "AAAAA", "sensor_id": "sensor_1111", "value": 22000}
{"timestamp": 1666660000, "user_id": "AAAAA", "sensor_id": "sensor_2222", "value": 22000}
{"timestamp": 1777770000, "user_id": "AAAAA", "sensor_id": "sensor_2222", "value": 25000}
{"timestamp": 1666660000, "user_id": "BBBBB", "sensor_id": "sensor_2222", "value": 30000}
{"timestamp": 1777770000, "user_id": "BBBBB", "sensor_id": "sensor_2222", "value": 40000}
When that two values coincide, I calculated the difference with the previous one.
And the elapsed time.
// RESULT
{"timestamp": 1555550000, "last_timestamp": 1000000000, "user_id": "AAAAA", "sensor_id": "sensor_1111", "value": 12000}
{"timestamp": 1777770000, "last_timestamp": 1666660000, "user_id": "AAAAA", "sensor_id": "sensor_2222", "value": 3000}
{"timestamp": 1777770000, "last_timestamp": 1666660000, "user_id": "BBBBB", "sensor_id": "sensor_2222", "value": 10000}
Something like this should be possible using a custom user defined aggregate function or UDAF.
If you were to write a Compare_to_previous UDAF which stores the first value it receives and compares it to the second value, then you could achieve what you want with:
CREATE TABLE DIFF AS SELECT
user_id,
sensor_id,
Compare_to_previous(ROWTIME) as last_timestamp,
Compare_to_previous(value) as value
GROUP BY user_id, sensor_id
HAVING Compare_to_previous(ROWTIME) IS NOT NULL;
Where compare_to_previous might be something like (not tested):
#UdafDescription(
name = "COMPARE_TO_PREVIOUS",
description = CompareToPrevious.DESCRIPTION
)
public final class CompareToPrevious {
static final String DESCRIPTION = "Blah blah";
// UDAF version for BIGINT:
private static Udaf<Long, List<Long>, Long> compareToPrevious() {
return new Udaf<Long, List<Long>, Long>() {
#Override
public List<Long> initialize() {
// Initial value is empty() meaning no previous.
return ImmutableList.of();
}
#Override
public List<Long> aggregate(final Long current, final List<Long> previous) {
if (current == null) {
// Ignore null values? You're choice!
return previous;
}
switch (previous.size()) {
case 0:
// 1st value seen for this key:
return ImmutableList.of(current);
case 1:
// 2nd value seen for this key:
return ImmutableList.of(previous.get(0), current);
default:
// Already seen two values - what to do?
// Do you want to compare pairs
// i.e. compare 2nd with 1st, then 4th with 3rd, etc.
// return ImmutableList.of()
// or
// Or each subsequent row with previous,
// i.e. you'd compare 2nd with 1st, then 3rd with 2nd etc.
return ImmutableList.of(previous.get(0), current);
}
}
#Override
public List<Long> merge(final List<Long> aggOne, final List<Long> aggTwo) {
throw new UnsupportedOperationException(
"Compare_to_previous won't work with session windowed data");
}
// Call to convert the intermediate List<Long> into an output value.
#Override
public Long map(final List<Long> agg) {
if (agg.size() != 2) {
// If we don't have two values to compare we output NULL.
// Use HAVING Compare_To_Previous(COL) IS NOT NULL in the query to exclude these null rows.
return null;
}
// We have two values - calculate the diff:
return ((long) agg.get(1)) - agg.get(0);
}
};
}
}
Such an aggregator won't be able to handle out-of-order data, but it will be suitable for many use cases.
Related
I am using Swashbuckle to generate Swagger output. On my model I the following property:
public FieldValue? MyProperty { get; set; }
FieldValue is a simple class that can hold an ID and a name:
public class FieldValue
{
/// <summary>
/// The numerical represenation of the value.
/// </summary>
/// <example>1</example>
public string Id { get; set; }
/// <summary>
/// The textual represenation of the value.
/// </summary>
/// <example>SomeValue</example>
public string Name { get; set; }
}
Via some business logic in our code, a set of possible key-value pairs (ID and name) are mapped to this property.
Now, I would like to show all the possible values for MyProperty in the enum tag in my Swagger output. Using an implementation of ISchemaFilter, a custom attribute on the MyProperty property and using allOf instead of a simple ref, I have managed to add the values in my JSON output, see below. The reason for using allOf instead of just ref is that ref will overwrite all properties on the referencing schema. So, when I use allOf it keeps my description and enum fields in the document.
{
"components": {
"schemas": {
"FieldValue": {
"type": "object",
"properties": {
"Id": {
"type": "string",
"description": "The numerical represenation of the value.",
"nullable": true,
},
"Name": {
"type": "string",
"description": "The textual represenation of the value.",
"nullable": true,
}
}
},
"MyProperty": {
"enum": [
"1: EnumValue1",
"2: EnumValue2",
"3: EnumValue3"
],
"allOf": [{
"$ref": "#/components/schemas/FieldValue"
}
],
"description": "Some description",
"nullable": true
}
}
}
This does not show the enum-values in the Swagger UI, though. It does show the description.
Is this Swagger doc valid? If not, how can I make it valid and display the enums for MyProperty?
The correct representation of your use case is:
"MyProperty": {
"enum": [
{"Id": "1", "Name": "EnumValue1"},
{"Id": "2", "Name": "EnumValue2"},
{"Id": "3", "Name": "EnumValue3"}
],
"allOf": [
{ "$ref": "#/components/schemas/FieldValue" }
],
"description": "Some description",
"nullable": true
}
However, there's little to no tooling support for enums containing object literals. I suggest mentioning the possible Id and Name values in the description of the MyProperty property.
Asp.net core 3.1 WebApi.
I have a model with required properties.
1.If model is not valid, then the response contains data
like :
{
"errors": {
"Name": [
"Update model can't have all properties as null."
],
"Color": [
"Update model can't have all properties as null."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|f032f1c9-4c36d1e62aa60ead."
}
And this looks good for me.
But if I add some custom validation to modelState.AddModelError("statusId", "Invalid order status id.") then it returns different structure:
[
{
"childNodes": null,
"children": null,
"key": "statusId",
"subKey": {
"buffer": "statusId",
"offset": 0,
"length": 8,
"value": "statusId",
"hasValue": true
},
"isContainerNode": false,
"rawValue": "11202",
"attemptedValue": "11202",
"errors": [
{
"exception": null,
"errorMessage": "Invalid order status id."
}
],
"validationState": 1
}
]
Also looks like ModelState.IsValid is actual no more for controller, because the bad request is returned before it even enters the controller with invalid mode. Or is there some flag with global validation via ModelSate?
Why the structure is different?
How to make it the same?
How to force to get to ModelState.IsValid method inside of api controller as it worked in MVC?
Update:
[Route("....")]
[Authorize]
[ApiController]
public class StatusesController : ApiControllerBase
{
[HttpPut, Route("{statusId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[Produces("application/json")]
public async Task<ObjectResult> UpdateStatusAsync(int statusId, [FromBody] StatusUpdateDto orderStatusUpdateDto)
{
int companyId = User.Identity.GetClaimValue<int>(ClaimTypes.CompanyId);
const string errorNotFound = "There is not order status with this id for such company";
if (statusId <= 0)
{
Logger.LogError(errorNotFound);
ModelState.AddErrorModel(nameof(statusId), "Invalid order status id")
throw new NotFound(ModelState);
}
if (orderStatusUpdateDto == null)
{
const string error = "Invalid (null) order status can't be added";
Logger.LogError(error);
throw new ArgumentNullException(error);
}
if (ModelState.IsValid == false) // also this code is always true or returns 400 before this line
{
return BadRequest(ModelState);
}
....
return result;
}
}
The ApiController attribute adds some specific opinionated behaviors to a controller. One of them is to return a 400 error if the model is not valid.
This behavior can be disabled, but only on a global level.
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
I think you have the following options:
Disable this behavior and check ModelState.IsValid yourself. Use ValidationProblem method to produce the same response
Add this check to the validator of the model
Keep everything as it is. But use ValidationProblem inside the controller method to return the validation errors.
See https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#automatic-http-400-responses for information
I have the following controller code
#GetMapping("/users")
public ResponseEntity<UserDto> getUsers(Filter filter) {
return ResponseEntity.ok(userService.findUsers(filter));
}
Filter.java:
public class Filter {
private Integer page;
private Integer size;
// Contains 2 fields: "propertyName" and "order"
private Sort sort;
... getters and setters
}
The URL is following: /users?page=1&size=10&sort=+firstName. So I have a custom converter from String to Sort and it works perfectly.
However, the generated swagger documentation is incorrect:
"parameters":[
{
"name":"sort.propertyName",
"in":"query",
"required":false,
"type":"string"
},
{
"name":"sort.order",
"in":"query",
"required":false,
"type":"string",
"enum":[
"+",
"-"
]
},
{
"name":"page",
"in":"query",
"required":false,
"type":"integer",
"format":"int32"
},
{
"name":"size",
"in":"query",
"required":false,
"type":"integer",
"format":"int32"
}
]
As you can see, it has broken down the Sort field of Filter and has generated 2 parameters: sort.propertyName and sort.order. Instead, I want to have one parameter sort with type string.
Is there any way to achieve that? I have tried annotating the sort field with #ApiParam(name = "sort", value = "Sort", type = "string"), but it doesn't work.
You could create an alternate type rule that treats Sort as a String
import static springfox.documentation.schema.AlternateTypeRules.*;
...
// Configure your docket bean
new Docket(...)
...
.alternateTypeRules(newRule(Sort.class, String.class)
...
I have Breeze.Sharp application which communicates with legacy WebAPI which doesn't provide metadata.
Query seems to be executed properly - expected number of objects is returned but all of them are empty. Modifying query parameters also works - number of returned objects changes as expected. I was playing around with EntityManager.MetadataStore but nothing helped.
Here is the final code I'm currently using to communicate with WebAPI.
public class DokumentModelBreeze: BaseEntity
{
public string id { get; set; }
public string numer { get; set; }
public decimal suma { get; set; }
}
...
Configuration.Instance.ProbeAssemblies(typeof(DokumentModelBreeze).Assembly);
var manager = new EntityManager("http://localhost:52357/api/");
manager.DataService.HasServerMetadata = false;
var store = manager.MetadataStore;
store.SetResourceName("dokumenty", typeof(DokumentModelBreeze), true);
store.NamingConvention = new CamelCasePropertiesNamingConvention();
var builder = new EntityTypeBuilder<DokumentModelBreeze>(store);
builder.DataProperty(d => d.id).IsPartOfKey();
using(TextWriter writer = File.CreateText("C:/metadata.txt")) {
store.ExportMetadata(writer);
var query = new EntityQuery<DokumentModelBreeze>("dokumenty");
query = query.WithParameter("nrFirmy", 1).Where(p => p.numer=="123");
var results = await manager.ExecuteQuery<DokumentModelBreeze>(query);
List<DokumentModelBreeze> Dokumenty = new List<DokumentModelBreeze>();
foreach(var item in results)
Dokumenty.Add(item);
In the last foreach loop every item is of type DokumentModelBreeze but each member property equals to null or 0 respectively.
I have saved MetadataStore to file, it is included below:
{
"metadataVersion": "1.0.3",
"namingConvention": {
"clientServerNamespaceMap": {},
"name": "camelCaseProperties"
},
"structuralTypes": [
{
"shortName": "BaseEntity",
"namespace": "Breeze.Sharp",
"baseTypeName": "",
"autoGeneratedKeyType": "None"
},
{
"shortName": "DokumentModelBreeze",
"namespace": "BRuNETWPF.ViewModels",
"baseTypeName": "BaseEntity:#Breeze.Sharp",
"autoGeneratedKeyType": "None",
"defaultResourceName": "dokumenty",
"dataProperties": [
{
"name": "id",
"dataType": "String",
"isNullable": false,
"defaultValue": "",
"isPartOfKey": true
},
{
"name": "numer",
"dataType": "String",
"isNullable": false,
"defaultValue": ""
},
{
"name": "suma",
"dataType": "Decimal",
"isNullable": false,
"defaultValue": 0.0
}
]
}
],
"resourceEntityTypeMap": {
"dokumenty": "DokumentModelBreeze:#BRuNETWPF.ViewModels"
}
}
Am I missing something here or perhaps Breeze# doesn't allow to query WebAPI without metadata ?
The same code executed against test WebAPI with exposed metadata works well.
Your GetValue and SetValue properties must be defined like so:
public string id
{
get { return GetValue<string>("id"); }
set { SetValue(value); }
}
It's a pain, I know, but this fixed it for me after one of our awesome tech leads pointed it out :)
I've been playing around with asp.net web api, and I noticed that default generated returned json doesn't include the object level key. I was also wondering how to customize the output of the json string. For example:
Getting a User usually returns
{
"Name": "ChaoticLoki",
"Age": 22,
"Sex": "Male"
}
I was hoping I could return something like:
{
"data": {
"Name": "ChaoticLoki",
"Age": 22,
"Sex": "Male",
},
"success": true
}
You can then create a class wrapping the data and status like this
public class JsonResult{
public object data { get; set;}
public boolean success { get; set;}
}
And then in your Web Api methods, you can do
var data = ... //getting from repo
return Json(new JsonResult{data = data, success = true});