khrt / Raisin

Raisin - a REST API micro framework for Perl 🐫 🐪
61 stars 29 forks source link

route_param regression from 0.86 -> 0.87 #84

Open djzort opened 4 years ago

djzort commented 4 years ago

This works in 0.86 but not in 0.87

resource 'etd' => sub {

    summary 'Get an estimate';
    desc 'Estimated Time of Delivery';
    route_param 'locale' => sub {
        route_param 'postcode' => sub {

            # query param style
            before_validation \&fix_material;
            params(
                requires( 'locale',   type => Locale,   desc => 'Locale (AU/NZ)' ),
                requires( 'postcode', type => PostCode, desc => 'Post Code' ),
                requires(
                    'material',
                    type => ArrayRef [MaterialCode],
                    desc => 'Material Code(s)'
                ),
                optional(
                    'orderdate',
                    type    => Str,
                    desc    => 'Order Date',
                    default => 'now'
                )
            );
            summary
              'GET one or more products via material code as query parameter';
            get \&guess_etd;

            # url path style
            params(
                requires( 'locale',   type => Locale,   desc => 'Locale (AU/NZ)' ),
                requires( 'postcode', type => PostCode, desc => 'Post Code' ),
                requires(
                    'material',
                    type => MaterialCode,
                    desc => 'Material Code'
                ),
                optional(
                    'orderdate',
                    type    => Str,
                    desc    => 'Order Date',
                    default => 'now'
                )
            );

            route_param 'material' => sub {
                summary 'GET a single product via material code in url';
                get \&guess_etd;
            };    # route_param 'material'

        };    # route_param 'postcode'
    };    # route_param 'locale'

};    # resource 'etd'

The api is as follows

/etd///

or

/etd///?material=&material=

Locale, PostCode, ad MaterialCode are custom constraints.

What doesnt work in 0.87 is the route_param version. The &guessetd is called BUT the $[0] is empty rather than containing the params as it does with the material in query query version

the fix_material sub massages the material= so that its always an arrayref even if there is just one value (which is an annoying bug of itself, as a single value will give a scalar and the type will fail)

khrt commented 4 years ago

I'll take a look ASAP. Thanks for reporting.

mschout commented 4 years ago

Maybe I misunderstand how route_param is supposed to be used, but aren't you supposed to pair route_param with a params() call describing the route param like this?

params( requires('locale', type => Locale, desc => '...') )
route_param locale => sub {
   ...
};
djzort commented 4 years ago

To me, its not clear how routeparams can be mixed with query params... but saying that the above code works as described in 0.86 and doesnt in 0.87. But strangely, it routes correctly in 0.87 but doesnt populate the $[0] with a hashref of parameters.

If i have misinterpreted the documentation and somehow relied upon non-intentional behaviour i am happy to change my code. But for now i have Raisin pinned to 0.86 in my cpanfile

mschout commented 4 years ago

Part of the problem is that nested route_param was broken badly in 0.86 and prior, in that the inner route_param would blow away all param definitions of previous route_param declarations.

This was fixed in 0.87.

I suspect this is what you need for 0.87:

resource 'etd' => sub {
    summary 'Get an estimate';
    desc 'Estimated Time of Delivery';
    params( requires( 'locale',  type => Locale, desc => 'Locale (AU/NZ)' ) );
    route_param 'locale' => sub {
        params( requires( 'postcode', type => PostCode, desc => 'Post Code' ) );
        route_param 'postcode' => sub {
           ...
            # query param style
            before_validation \&fix_material;
            params(
                requires( 'material', ... );
                optional( 'orderdate', ... );
            );
            summary 'GET one or more products via material code as query parameter';
            get \&guess_etd;

            # url path style
            params( requires( 'material', ... ) );
            route_param 'material' => sub {
                summary 'GET a single product via material code in url';
                params( optional( 'orderdate', ... ) ):
                get \&guess_etd;
            };    # route_param 'material'
        };    # route_param 'postcode'
    };    # route_param 'locale'
};    # resource 'etd'

And as a nice benefit you don't have to repeat the postcode and locale params for every internal endpoint.

But per the raisin docs, preceding the route_param with a params() call that describes the route param seems to be the recommended way to deal with route params.

djzort commented 4 years ago

Ok this seems to work but has the annoying side effect that previously if the route_param value type didnt match the type it would return 400. Where as now it returns a 404. I am guessing this is due to failing to match causing it to not route past the handler code.

khrt commented 4 years ago

That indeed is not fixed yet. It's on my roadmap.

matya commented 3 years ago

Just out of curiosity, because I did not find it anywhere, and before digging into the code, I would ask: How do you access the postcode and locale variables in guess_etd? From what I see while playing around with it is that the get sub's first param is only returning the params on the level it is defined...

khrt commented 3 years ago

@matya,

that would be something like:

get sub {
        my $params = shift;
        $params->{postcode};
        $params->{locale};
        $params->{...};
}

Where $params is a hashref to all the parameters parsed by: https://github.com/khrt/Raisin/blob/5f77df30dcaa41b293b0037b1e64935462d1d30f/lib/Raisin/Request.pm#L12

The problem here must be that nested path arguments are not stored properly and/or got overwritten somewhere inside that function: https://github.com/khrt/Raisin/blob/5f77df30dcaa41b293b0037b1e64935462d1d30f/lib/Raisin/API.pm#L102

This would be the patch which introduced the issue: https://github.com/khrt/Raisin/commit/06d50a9511ec11ea82eb5de9e2820479cd5e36bb

matya commented 3 years ago

my bad, I messed up the params block. thanks! I have found some examples I was looking for in the test cases (which I didn't think of looking at).