Closed blablabla1234678 closed 3 months ago
I think the output should be a bunch of constraints according to #2 The structure can be flatten if we insist to keep the selector array. Something like
{
type: "Array",
item: {
type: "Object",
items: {
name: {type: "String"}
}
}
items: [
{
name: "Carl Johnson",
},
// ...
]
}
Should result the following collection after parsing:
[
new TypeConstraint([], "array"),
new TypeConstraint(["item"], "object"),
new TypeConstraint(["item","name"], "string"),
new ValueConstraint(["items",0], {});
new ValueConstraint(["items",0,"name"], "Carl Johnson");
]
I think this is all the parser should do. The interpreter should check if there is constraint violation.
Currently I test only the parser, so I'll deal with the interpreter later.
How to handle variables?
{
type: "Array",
item: {
type: "Person"
},
items: [
{
name: "Carl Johnson",
},
// ...
],
Person: {
type: "Object",
items: {
name: {
type: "Name"
}
}
},
Name: {
type: "String",
length: [3, 255]
},
}
[
new TypeConstraint([], "array"),
new TypeConstraint(["item"], "Object"),
new TypeConstraint(["item","name"], "String"),
new ValueConstraint(["items",0], {}),
new ValueConstraint(["items",0,"name"], "Carl Johnson"),
]
[
new TypeConstraint([], "array"),
new TypeConstraint(["item"], "Person"),
new ValueConstraint(["items"], [{name:"Carl Johnson"}]),
new Definition("Person"),
new TypeConstraint(["Person"], "Object"),
new TypeConstraint(["Person","name"], "Name"),
new Definition("Name"),
new TypeConstraint(["Name"], "String"),
new StringLengthConstraint(["Name"], [3, 255]),
]
Maybe easier to interpret this way:
[
new TypeConstraint([], "array"),
new TypeConstraint(["item"], "Person"),
new ValueConstraint(["items"], [{name:"Carl Johnson"}]),
new Definition("Person", [
new TypeConstraint([], "Object"),
new TypeConstraint(["name"], "Name"),
]),
new Definition("Name", [
new TypeConstraint([], "String"),
new StringLengthConstraint([], [3, 255]),
]),
]
I don't think I want another JSON-LD or anything similar, so I think separation of values and definitions would be useful.
documentation:
[
new TypeConstraint([], "array"),
new TypeConstraint(["item"], "Person"),
new Definition("Person", [
new TypeConstraint([], "Object"),
new TypeConstraint(["name"], "Name"),
]),
new Definition("Name", [
new TypeConstraint([], "String"),
new StringLengthConstraint([], [3, 255]),
]),
]
values:
{
items: [
{name:"Carl Johnson", hyperlinks: []}
],
hyperlinks: []
}
The client will know how to validate the response and build the request, because the hyperlink type is documented along with the request and response types. The client should not follow a hyperlink that is not documented. It is not even possible to pass an undocumented hyperlink, because they should look like this:
hyperlinks: [
{type: "updateUser", parameters: {id: 1, name: "Carl Johnson"}}
]
Yet another option for the parser.
[
new TypeConstraint([], "array"),
new TypeConstraint(["item"], "Person"),
new ValueConstraint(["items"], [{name:"Carl Johnson"}]),
new TypeConstraint([{type:"Person"}], "Object"),
new TypeConstraint([{type:"Person"},"name"], "Name"),
new TypeConstraint([{type:"Name"}], "String"),
new StringLengthConstraint([{type:"Name"}], [3, 255]),
]
I think we need to split this up into variables. Another thing that we must separate actual data and documentation metadata. In JSON-LD they are not separated which makes things hard to grasp, at least for me certainly.
const response = {
items: [
{
name:"Carl Johnson",
hyperlinks: []
}
],
hyperlinks:[]
};
const type = "listPeopleResponse";
const docs= {};
docs.listPeopleResponse = [
new TypeConstraint([], "Array"),
new CustomTypeConstraint(["item"], "Person"),
];
docs.Person = [
new TypeConstraint([], "Object"),
new CustomTypeConstraint(["name"], "Name"),
];
docs.Name = [
new TypeConstraint([], "String"),
new StringLengthConstraint([], [3, 255]),
];
As of the documentation it can be used to build different clients. For example we can build view, and we can build type check with it.
So for example we can do
view.generateTemplate(docs.listPeopleResponse).fill(response);
const response = await client.follow(main.find(docs.listPeopleResponse).fill({page:10}));
The upper solution is certainly not good. I mean this one:
docs.listPeopleResponse = [
new TypeConstraint([], "Array"),
new CustomTypeConstraint(["item"], "Person"),
];
docs.Person = [
new TypeConstraint([], "Object"),
new CustomTypeConstraint(["name"], "Name"),
];
docs.Name = [
new TypeConstraint([], "String"),
new StringLengthConstraint([], [3, 255]),
];
We need to resolve this and get a nice tree instead of a graph.
docs.listPeopleResponse = [
new TypeConstraint([], "Array"),
new TypeConstraint(["item"], "Object"),
new TypeConstraint(["item","name"], "String"),
new StringLengthConstraint(["item","name"], [3, 255]),
];
docs.Person = [
new TypeConstraint([], "Object"),
new TypeConstraint(["name"], "String"),
new StringLengthConstraint(["name"], [3, 255]),
];
docs.Name = [
new TypeConstraint([], "String"),
new StringLengthConstraint([], [3, 255]),
];
It increases memory consumption though.
Resolving takes a loop which should run after the document is parsed and pushes the constraints of the referenced variable to the existing ones. After it we must sort the constraints as well.
To reduce memory consumption we can use the original variables instead of copying them. So that problem is solved. Merging must be done somehow and addition is the simplest way. I would check if constraints are compatible with each other, but if there is contradiction, then it will turn out by checking the values.
The problematic part with this approach that we cannot use custom view panels e.g. for Person because the context won't contain that term. So we need to think again.
Another option is building a chain of types. The return value can contain a CustomTypeConstraint
for each of them. So we can still have view panels and use the name of the custom type for validation. I think this is ok too.
There are constraint dependencies which we have not handled yet. E.g. for Type the dependency is Required, for StringLength the dependency is Type:String. Not sure if we can reuse the current constraints, but it would be nice.
A prototype for type check engine:
var context = {
a: {
type: "b"
},
b: {
type: "c"
},
c: {
type: "native"
}
};
var value = "x";
function check(value, definition, context, stack){
if (definition.type !== "native"){
if (stack[definition.type])
throw new Error('Infinite loop');
stack[definition.type] = true;
return check(value, context[definition.type], context, stack);
}
else {
return "valid";
}
}
console.log(check(value, context.a, context, {}));
I think this should be generalized further to support both views and checks.
Slightly upgraded engine:
var context = {
a: {
type: "b"
},
b: {
type: "c"
},
c: {
type: "String"
},
String: {
native: true
}
};
var value = "x";
class Engine {
constructor(context, task){
this.context = context;
this.task = task;
}
process(value, type, result){
var stack = {};
this.processVariable(value, type, stack, result);
return result;
}
processVariable(value, type, stack, result){
const definition = this.context[type];
if (stack[definition.type])
throw new Error('Infinite loop');
stack[definition.type] = true;
this.task(value, type, definition, result);
if (!definition.native && definition.type)
this.processVariable(value, definition.type, stack, result);
}
}
var engine = new Engine(context, function (value, name, definition, result){
result.push({type: name, valid: true});
});
console.log(engine.process(value, "a", []));
I developed this further. Looks ugly atm. but works.
So the engine uses the documentation to process the values. We parse only the relevant part of the documentation instead of the complete documentation. The engine can be used to process the complete documentation as well. The engine can be used to make the constraint array if we want to.
Not sure about what the current code should do, I need to think about it.