Let's say I have a command-object containing seven fields(String monday, String tuesday, ..) and I need to validate it just to check if at least one of them exists.
Because of working with Grails 1.3.7 I tried to use instance validation with extended-validation-plugin(not rich-domain) but I couldn't make it work. Basically, it does not recognise non-field validator inside of static constraints block.
static constraints = {
availabilitySelected(validator: {
...
})
...
and I get:
Exception Message: No such property: availabilitySelected
Is there any other smart way to do it? I just do not want to add validator for every single field in my command object.
Thanks
You can access the object being validated via the two / three parameter form of a custom validator.
myField(validator: { val, obj, errors -> if( obj.blah.empty && obj.blah.empty ) { errors.reject( ... ) } )
http://grails.org/doc/1.3.7/ref/Constraints/validator.html
Related
I have a SlickGrid Table, in which there are compound filters, currently when i try to change the compound filter (lets say from Equal To to Less Than), then it makes an API call.
I don't want to make an API call, how do i achieve this?
I searched in slickgrid docs, but couldn't find any property(if it is available).
Image
Please note that I'm the author of Angular-Slickgrid
So I looked at the problem you're having and it seems like a valid problem to look into, I agree that for some filters like the Compound Date Filter Operator we shouldn't query right away, that is after changing a the operator dropdown without providing a date. So, for that reason I am adding a new grid option skipCompoundOperatorFilterWithNullInput which will avoid triggering a filter change (it will also avoid querying the backend when implemented) when we first change the operator dropdown without providing a date being entered.
Note that this new option will only be available with Angular-Slickgrid v5.1.0+ (via this PR, now supports this and it will only be enabled by default on the Compound Date Filter (any other filters will have to explicitly enable this new flag either via grid option or via a column definition).
What if I cannot upgrade to 5.1.0? Are there any other ways of dealing with this?
Yes, it's just a bit more involving dealing with this though, it however requires a lot more work from your side. The information you need to know is that nearly every piece of code from Angular-Slickgrid and Slickgrid-Universal are protected TypeScript classes and functions which mean that you can simply use TypeScript to extends any of them. Let's take for example the CompoundDateFilter class, we could extend it this way to skip the callback triggering without a date provided (this._currentDate)
import { CompoundDateFilter, OperatorString } from '#slickgrid-universal/common';
export class CustomCompoundDateFilter extends CompoundDateFilter {
protected onTriggerEvent(e: Event | undefined) {
if (this._clearFilterTriggered) {
this.callback(e, { columnDef: this.columnDef, clearFilterTriggered: this._clearFilterTriggered, shouldTriggerQuery: this._shouldTriggerQuery });
this._filterElm.classList.remove('filled');
} else {
const selectedOperator = this._selectOperatorElm.value as OperatorString;
(this._currentValue) ? this._filterElm.classList.add('filled') : this._filterElm.classList.remove('filled');
// -- NEW CODE BELOW -- (to skip triggering callback on undefined date)
// when changing compound operator, we don't want to trigger the filter callback unless the date input is also provided
if (this._currentDate !== undefined) {
this.callback(e, { columnDef: this.columnDef, searchTerms: (this._currentValue ? [this._currentValue] : null), operator: selectedOperator || '', shouldTriggerQuery: this._shouldTriggerQuery });
}
}
this._clearFilterTriggered = false;
this._shouldTriggerQuery = true;
}
}
then use this new custom filter class in your column definitions
import { CustomCompoundDateFilter } from './custom-compoundDateFilter';
initGrid() {
this.columnDefinitions = [{
id: 'start', name: 'Start', field: 'start',
filterable: true, filter: { model: CustomCompoundDateFilter },
}];
}
and there you have it, below is a proof that it is working since I changed the operator and as you can see below this action is no longer resulting in 0 row returned. However if I had done the inverse, which is to input the date but without an operator, it would have execute the filtering because "no operator" is defaulting to the "equal to" operator.
I am trying to expose a rest endpoint with camel. It will show a json data which is inside some .json files stored in s3 bucket. Also, it will filter by a date range.
First, I got some s3 objects informations in my Camel routes. (I am using kotlin)
//expose the endpoint
from("jetty:http://0.0.0.0:8080/getObjects")
.routeId("list-objects-on-bucket")
.to("aws-s3://[bucket-name]?amazonS3Client=#s3Client&operation=listObjects")
.process(ListObjects())
.to("direct:filter-list-from-s3")
then, I filter the data. (Till here everything is alright)
from("direct:filter-list-from-s3")
.routeId("filter-list-from-s3")
.process(FilterObjects())
.to("log:info")
But in my FilterObject class I do not know how to download every files that matches (look the if statement) and pass it to the next route that will treat them
class SaoMateusFilterObjects : Processor {
override fun process(exchange: Exchange?) {
val start_date = exchange!!.getIn().getHeader("start_date") as String
val end_date = exchange.getIn().getHeader("end_date") as String
val formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy")
val start = LocalDate.parse(start_date).format(formatter)
val end = LocalDate.parse(end_date).format(formatter)
val objectsNames = exchange!!.getIn().body as LinkedList<String>
for (objectName in objectsNames) {
if(objectName.contains(start) && objectName.contains(end) && objectName.contains(".json")) {
exchange.getIn() to "aws-s3://[bucket-name]?amazonS3Client=#s3Client&operation=getObject&fileName=$objectName"
}
}
}
}
Some problems are:
1 - I want to read. By I think I can't use the from() method. Because it can be use just once. So, the to() method is used to read.
2 - exchange.getIn().to("[s3-uri]") maybe/must be converted in S3Object(). How??
Can Someone help me with this?
Thank you
Instead of .to route, use .bean() and use the s3.getObject method to get the S3Object.
always Prefer using .bean() over .processor().
offical_s3_java_object operation sample.
TLNR: I was trying to test DTO validation in the controller spec instead of in e2e specs, which are precisely crafted for that. McDoniel's answer pointed me to the right direction.
I develop a NestJS entrypoint, looking like that:
#Post()
async doStuff(#Body() dto: MyDto): Promise<string> {
// some code...
}
I use class-validator so that when my API receives a request, the payload is parsed and turned into a MyDto object, and validations present as annotations in MyDto class are performed. Note that MyDto has an array of nested object of class MySubDto. With the #ValidateNested and #Type annotations, the nested objects are also validated correctly.
This works great.
Now I want to write tests for the performed validations. In my .spec file, I write:
import { validate } from 'class-validator';
// ...
it('should FAIL on invalid DTO', async () => {
const dto = {
//...
};
const errors = await validate( dto );
expect(errors.length).not.toBe(0);
}
This fails because the validated dto object is not a MyDto. I can rewrite the test as such:
it('should FAIL on invalid DTO', async () => {
const dto = new MyDto()
dto.attribute1 = 1;
dto.subDto = { 'name':'Vincent' };
const errors = await validate( dto );
expect(errors.length).not.toBe(0);
}
Validations are now properly made on the MyDto object, but not on my nested subDto object, which means I will have to instantiate aaaall objects of my Dto with according classes, which would be much inefficient. Also, instantiating classes means that TypeScript will raise errors if I voluntarily omits some required properties or indicate incorrect values.
So the question is:
How can I use NestJs built-in request body parser in my tests, so that I can write any JSON I want for dto, parse it as a MyDto object and validate it with class-validator validate function?
Any alternate better-practice ways to tests validations are welcome too!
Although, we should test how our validation DTOs work with ValidationPipe, that's a form of integration or e2e tests. Unit tests are unit tests, right?! Every unit should be testable independently.
The DTOs in Nest.js are perfectly unit-tastable. It becomes necessary to unit-test the DTOs, when they contain complex regular expressions or sanitation logic.
Creating an object of the DTO for test
The request body parser in Nest.js that you are looking for is the class-transformer package. It has a function plainToInstance() to turn your literal or JSON object into an object of the specified type. In your example the specified type is the type of your DTO:
const myDtoObject = plainToInstance(MyDto, myBodyObject)
Here, myBodyObject is your plain object that you created for test, like:
const myBodyObject = { attribute1: 1, subDto: { name: 'Vincent' } }
The plainToInstance() function also applies all the transformations that you have in your DTO. If you just want to test the transformations, you can assert after this statement. You don't have to call the validate() function to test the transformations.
Validating the object of the DTO in test
To the emulate validation of Nest.js, simply pass the myDtoObject to the validate() function of the class-validator package:
const errors = await validate(myDtoObject)
Also, if your DTO or SubDTO object is too big or too complex to create, you have the option to skip the remaining properties or subObjects like your subDto:
const errors = await validate(myDtoObject, { skipMissingProperties: true })
Now your test object could be without the subDto, like:
const myBodyObject = { attribute1: 1 }
Asserting the errors
Apart from asserting that the errors array is not empty, I also like to specify a custom error message for each validation in the DTO:
#IsPositive({ message: `Attribute1 must be a positive number.` })
readonly attribute1: number
One advantage of a custom error message is that we can write it in a user-friendly way instead of the generic messages created by the library. Another big advantage is that I can assert this error message in my tests. This way I can be sure that the errors array is not empty because it contains the error for this particular validation and not something else:
expect(stringified(errors)).toContain(`Attribute1 must be a positive number.`)
Here, stringified() is a simple utility function to convert the errors object to a JSON string, so we can search our error message in it:
export function stringified(errors: ValidationError[]): string {
return JSON.stringify(errors)
}
Your final test code
Instead of the controller.spec.ts file, create a new file specific to your DTO, like my-dto.spec.ts for unit tests of your DTO. A DTO can have plenty of unit tests and they should not be mixed with the controller's tests:
it('should fail on invalid DTO', async () => {
const myBodyObject = { attribute1: -1, subDto: { name: 'Vincent' } }
const myDtoObject = plainToInstance(MyDto, myBodyObject)
const errors = await validate(myDtoObject)
expect(errors.length).not.toBe(0)
expect(stringified(errors)).toContain(`Attribute1 must be a positive number.`)
}
Notice how you don't have to assign the values to the properties one by one for creating the myDtoObject. In most cases, the properties of your DTOs should be marked readonly. So, you can't assign the values one by one. The plainToInstance() to the rescue!
That's it! You were almost there, unit testing your DTO. Good efforts! Hope that helps now.
To test input validation with the validation pipes, I think it is agreed that the best place to do this is in e2e tests rather than in unit tests, just make sure that you remember to register your pipes (if you normally use app.useGlobalPipes() instead of using dependency injection)
I defined a new type of model element as a plug-in; let's refer to it as Foo. A Foo node in the model should translate to a section element in the view. So far, so good. I managed to do that by defining simple conversion rules. I also managed to define a new FooCommand that transforms (renames) selected blocks to Foo.
I got stuck trying to have attributes on those Foo model nodes be translated to attributes on the view elements (and vice-versa). Suppose Foos have an attribute named fooClass which should map to the view element's class attribute.
<Foo fooClass="green-foo"> should map to/from <section class="green-foo">
I can successfully receive parameters in FooCommand, but I can't seem to set them on the blocks being processed by the command:
execute(options = {}) {
const document = this.editor.document;
const fooClass = options.fooClass;
document.enqueueChanges(() => {
const batch = options.batch || document.batch();
const blocks = (options.selection || document.selection).getSelectedBlocks();
for (const block of blocks) {
if (!block.is('foo')) {
batch.rename(block, 'foo');
batch.setAttribute(block, 'fooClass', fooClass);
}
}
});
}
Below is the code for the init function in the Foo plugin, including the model→view and view→model conversions:
init() {
const editor = this.editor;
const doc = editor.document;
const data = editor.data;
const editing = editor.editing;
editor.commands.add('foo', new FooCommand(editor));
doc.schema.registerItem('foo', '$block');
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
const fooClass = modelElement.item.getAttribute('fooClass'));
return new ContainerElement('section', {'class': fooClass});
});
buildViewConverter().for(data.viewToModel)
.fromElement('section')
.toElement(viewElement => {
let classes = Array.from(viewElement.getClassNames());
let modelElement = new ModelElement('foo', {'fooClass': classes[0]});
return modelElement;
});
}
When I try to run the command via
editor.execute('foo', { fooClass: 'green-foo' })
I can see that the green-foo value is available to FooCommand, but the modelElement in the model→view conversion, on the other hand, has no fooClass attribute.
I'm sure I'm missing the point here and misusing the APIs. I'd be really thankful if someone could shed some light on this issue. I can provide more details, as needed.
Follow-up after initial suggestions
Thanks to #Reinmar and #jodator for their suggestion regarding configuring the document schema to allow for the custom attribute. I really thought that would have taken care of it, but no. It may have been a necessary step anyway, but I'm still unable to get the attribute value from the model element during the model→view conversion.
First, let me add an important piece of information I had left out: the CKEditor5's version I'm working with is 1.0.0-alpha2. I am aware several of the APIs are bound to change, but I would still like to get things working with the present version.
Model→view conversion
If I understand it correctly, one can either pass a string or a function to the toElement call. A question about using the latter: what exactly are the parameters passed to the function? I assumed it would be the model element (node?) to be converted. Is that the case? If so, why is the attribute set on that node via batch.setAttribute (inside a document.enqueueChanges) not available when requested? Should it be?
A sequencing problem?
Additional testing seems to indicate there's some kind of order-of-execution issue happening. I've observed that, even though the attribute is not available when I first try to read it from the modelElement parameter, it will be so if I read it again later. Let me try to illustrate the situation below. First, I'll modify the conversion code to make it use some dummy value in case the attribute value is not available when read:
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
let fooClass = modelElement.item.getAttribute('fooClass') || 'naught';
let viewElement = new ContainerElement('section');
viewElement.setAttribute('class', fooClass);
return viewElement;
});
Now I reload the page and execute the following instructions on the console:
c = Array.from(editor.document.getRoot().getChildren());
c[1].is('paragraph'); // true
// Changing the node from 'paragraph' to 'foo' and adding an attribute
// 'fooClass' with value 'green-foo' to it.
editor.document.enqueueChanges(() => {
const batch = editor.document.batch();
batch.rename(c[1], 'foo');
batch.setAttribute(c[1], 'fooClass', 'green-foo');
return batch;
});
c[1].is('paragraph'); // false
c[1].is('foo'); // true
c[1].hasAttribute('fooClass'); // true
c[1].getAttribute('fooClass'); // 'green-foo'
Even though it looks like the expected output is being produced, a glance at the generated view element shows the problem:
<section class="naught"/>
Lastly, even if I try to reset the fooClass attribute on the model element, the change is not reflected on the view element. Why is that? Shouldn't changes made via enqueueChanges cause the view to update?
Sorry for the very long post, but I'm trying to convey as many details as I can. Here's hoping someone will spot my mistake or misunderstanding of how the CKEditor 5's API actually works.
View not updating?
I turned to Document's events and experimented with the changesDone event. It successfully addresses the "timing" issue, as it consistently triggers only after all changes have been processed. Still, the problem of the view not updating in response to a change in the model remains. To make it clear, the model does change, but the view does not reflect that. Here is the call:
editor.document.enqueueChanges(() => editor.document.batch().setAttribute(c[1], 'fooClass', 'red-foo'));
To be 100% sure I wrote the whole feature myself. I use the 1.0.0-beta.1 API which is completely different than what you had.
Basically – it works. It isn't 100% correct yet, but I'll get to that.
How to convert an element+attribute pair?
The thing when implementing a feature which needs to convert element + attribute is that it requires handling the element and attribute conversion separately as they are treated separately by CKEditor 5.
Therefore, in the code below you'll find that I used elementToElement():
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
So a converter between model's <foo> element and view's <section> element. This is a two-way converter so it handles upcasting (view -> model) and downcasting (model -> view) conversion.
NOTE: It doesn't handle the attribute.
Theoretically, as the view property you could write a callback which would read the model element's attribute and create view element with this attribute set too. But that wouldn't work because such a configuration would only make sense in case of downcasting (model -> view). How could we use that callback to downcast a view structure?
NOTE: You can write converters for downcast and upcast pipelines separately (by using editor.conversion.for()), in which case you could really use callbacks. But it doesn't really make sense in this case.
The attribute may change independently!
The other problem is that let's say you wrote an element converter which sets the attribute at the same time. Tada, you load <section class=ohmy> and gets <foo class=ohmy> in your model.
But then... what if the attribute will change in the model?
In the downcast pipeline CKEditor 5 treats element changes separately from attribute changes. It fires them as separate events. So, when your FooCommand is executed on a heading it calls writer.rename() and we get the following events in DowncastDispatcher:
remove with <heading>
insert:section
But then the attribute is changed too (writer.setAttribute()), so we also get:
setAttibute:class:section
The elementToElement() conversion helper listens to insert:section event. So it's blind to setAttribute:class:selection.
Therefore, when you change the value of the attribute, you need the attributeToAttribute() conversion.
Sequencing
I didn't want to reply to your question before we released 1.0.0-beta.1 because 1.0.0-beta.1 brought the Differ.
Before 1.0.0-beta.1 all changes were converted immediately when they were applied. So, rename() would cause immediate remove and insert:section events. At this point, the element that you got in the latter one wouldn't have the class attribute set yet.
Thanks to the Differ we're able to start the conversion once all the changes are applied (after change() block is executed). This means that the insert:section event is fired once the model <foo> element has the class attribute set already. That's why you could write a callback-based converters... bur you shouldn't :D
The code
import { downcastAttributeToAttribute } from '#ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
import { upcastAttributeToAttribute } from '#ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
class FooCommand extends Command {
execute( options = {} ) {
const model = this.editor.model;
const fooClass = options.class;
model.change( writer => {
const blocks = model.document.selection.getSelectedBlocks();
for ( const block of blocks ) {
if ( !block.is( 'foo' ) ) {
writer.rename( block, 'foo' );
writer.setAttribute( 'class', fooClass, block );
}
}
} );
}
}
class FooPlugin extends Plugin {
init() {
const editor = this.editor;
editor.commands.add( 'foo', new FooCommand( editor ) );
editor.model.schema.register( 'foo', {
allowAttributes: 'class',
inheritAllFrom: '$block'
} );
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
editor.conversion.for( 'upcast' ).add(
upcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
editor.conversion.for( 'downcast' ).add(
downcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
// This should work but it does not due to https://github.com/ckeditor/ckeditor5-engine/issues/1379 :(((
// EDIT: The above issue is fixed and will be released in 1.0.0-beta.2.
// editor.conversion.attributeToAttribute( {
// model: {
// name: 'foo',
// key: 'class'
// },
// view: {
// name: 'section',
// key: 'class'
// }
// } );
}
}
This code works quite well, except the fact that it converts the class attribute on any possible element that has it. That's because I had to use very generic downcastAttributeToAttribute() and upcastAttributeToAttribute() converters because of a bug that I found (EDIT: it's fixed and will be available in 1.0.0-beta.2). The commented out piece of code is how you it should be defined if everything worked fine and it will work in 1.0.0-beta.2.
It's sad that we missed such a simple case, but that's mainly due to the fact that all our features... are much more complicated than this.
I have got a domain class with some custom validators like the following:
class Domain {
String attribute1
OtherDomain attribute2
static constraints = {
attribute2 nullable: true, validator: {OtherDomain od, Domain d ->
if (od) {
log.debug "entering validation"
// certain validation here
}
}
}
For updating I have got a simple action in the corresponding DomainController:
#Transactional
def update(Domain domainInstance) {
log.debug "entering update()"
// rest of update
}
I'm wondering why in my debuglog I get the debug messages in the following order:
entering validation
entering update()
The problem is that the validation fails at this point (StackOverflowError). I know the reason for this error and I know what to do to circumvent this error (doing so within update action). However, I don't know why there is a validation before the programme even gets into the update() action. And I don't know how to prevent a validation at this point.
Do you have any suggestions?
The reason you see the "entering validation" message before "entering update()" is because you have declared a command object of type Domain. This means that Grails will bind any request parameters to domainInstance and then call validate() on it, before the action is executed. This allows you to write code like this:
#Transactional
def update(Domain domainInstance) {
// at this point request params have been bound and validate() has
// been executed, so any validation errors will be available in
// domainInstance.errors
if (domainInstance.hasErrors() {
// do something
}
log.debug "entering update()"
}