#1 2024-05-11 20:26:26

Lauri
Member
From: Under a rock
Registered: 2020-12-05
Posts: 25

Interface method variable input parameters

Hi!

Continuing discussion from email as suggested by ab.

Arnaud wrote:

If you want undefined variable inputs, the best is to use a sub-parameter of type variant, so you can send some JSON object with this parameter, and retrieve it as a TDocVariantData at runtime.

I'm not exactly sure if i understood correctly, but the following works and seems to fit the description:
Given interface procedure(const Vars: variant);
For GET, had to put params like this API/Interface?Vars={Abc=123,Def=321}
For POST, such json body is needed for same result: {"Vars":{"Abc":123,"Def":321}}

Did i get that correct?

If yes, then i don't not like it! smile It annoys me to write artificial elements like that. Having variable inputs is not so unusual requirement i think.
So since i dont like, in order not to be a whiner, must try to improve smile
Since there are already special interface method names like GET_ and POST_, i thought why not use a special variable name to indicate that all input is to be passed along.

I made small changes in two mORMot units. And now i can declare such interface: procedure(const _AllInputAs: variant);
And call like so:
For GET, URI params simply Abc=123&Def=321
For POST, json body {"Abc":123,"Def":321}
The parameters are parsed into a docvariant and can be enumerated in the implementing method.

The conditions are that an interface must contain one input and its name must be "_AllInputAs" and its type must be variant. Unfortunately this could not be achieved by overriding classes. Here are the changes i made (based on version 2.2 stable).

Changed in mormot.core.interfaces.pas:

313,315d312
<     /// flag indicating first parameter of this method is const _AllInputAs: variant
<     // - used for passing undefined input parameters via GET/POST  
<     ArgsWantAllInputAs: boolean;
4002,4007d3998
<     // is this _AllInputAs interface method?
<     ArgsWantAllInputAs :=
<       (ArgsInputValuesCount = 1) and
<       (Args[ArgsInFirst].ValueDirection = imdConst) and
<       (Args[ArgsInFirst].ParamName^ = '_AllInputAs') and
<       (Args[ArgsInFirst].ValueType = imvVariant);
7476,7477c7467
<     if (not fMethod^.ArgsWantAllInputAs) and
<        (fMethod^.ArgsInputValuesCount <> 0) and
---
>     if (fMethod^.ArgsInputValuesCount <> 0) and
7530,7535d7519
<     if fMethod^.ArgsWantAllInputAs and (P <> nil) then
<     begin
<       ctxt.InitParser(P, nil, fFactory.JsonParserOptions, @fDocVariantOptions, nil, nil);
<       fMethod^.Args[fMethod^.ArgsInFirst].SetFromJson(ctxt, fMethod, fValues[fMethod^.ArgsInFirst], Error);
<     end
<     else

Changed in mormot.rest.server.pas:

4526,4536d4525
<   // _AllInputAs magic parameter for interface method
<   m := ServiceMethod;
<   if
<     (m.ArgsInputValuesCount = 1) and
<     (m.Args[m.ArgsInFirst].ValueDirection = imdConst) and
<     // a flag set at routes init time would be better than compare here
<     (m.Args[m.ArgsInFirst].ParamName^ = '_AllInputAs')
<   then begin
<     fCall^.InBody := GetInputAsTDocVariant([], nil);
<     exit;
<   end;

Terrible idea?

Edit:
Added test project:
https://gist.github.com/lzva/4756328024 … 0105f11601

Last edited by Lauri (2024-05-11 21:22:57)

Offline

#2 2024-05-12 13:48:02

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,655
Website

Re: Interface method variable input parameters

Sounds like trying to make a circle enter into a rectangle hole....

Perhaps you could just use not interface-based services, but a method-based service, or directly register a route at HTTP server level.

The only fact that you need a kind of "varying" input smells to me like a poorly defined API.
IMHO an API should follow the "design by contract" pattern, i.e. it should do one thing, with no confusion possible.

Offline

#3 2024-05-13 23:35:15

Lauri
Member
From: Under a rock
Registered: 2020-12-05
Posts: 25

Re: Interface method variable input parameters

ab wrote:

Sounds like trying to make a circle enter into a rectangle hole....

I will refrain from commenting that one big_smile

ab wrote:

Perhaps you could just use not interface-based services, but a method-based service, or directly register a route at HTTP server level.

Sure, i could. But this way seems more convenient and the declaration expresses the intent better.
It seems like a basic feature to me that should be built-in, not reinvented each time (i searched, there have been others asking for this).

ab wrote:

The only fact that you need a kind of "varying" input smells to me like a poorly defined API.
IMHO an API should follow the "design by contract" pattern, i.e. it should do one thing, with no confusion possible.

In our product, customers can create their own extra fields for users table. This feature has been a huge success. It has helped keep the base package design clean and we don't have to worry about crazy customer ideas. Someone wants a column for employee waist size, no problem! smile
The API must also support this feature so customers can create integrations without directly querying our db.
So for user create and modify, must support inputs that match these unknown column names.
Is varying inputs poor for that? What would be better solution?

Offline

Board footer

Powered by FluxBB