Closed osher closed 7 years ago
baaah. found a workaround.
lib/fittings/cors-patch.js
in user-project as:
module.exports = require('swagger-node-runner/fittings/cors');
where config/default.yaml
states
swagger:
fittingsDirs: [ lib/fittings, node_modules ]
...
bagpipes:
...
cors:
name: cors-patch # <==== ssswell....
origin:
- !!js/regexp /^https?:\/\/[0-9a-z-.]+\.mydomain.com(:\d*)?$/i
Ugh. Yeah, I'd strongly recommend not putting "node_modules" in the fittingsDirs because of the potential name conflicts.
Oh... :disappointed: ...but I need fitting to be modular and reusable...
What would you recommend?
I'll give you more information.
My task is to provide a repository of modular and reusable infrastructure parts to implement a big set of micro-services. On the surface, out of the premise - Pipes and Fittings look great for this purpose :smile:.
These infrasturcure parts may be:
This, on top of the must-for-all basics:
I thought that each pipe may enrich the context with an initiated module / instance, resulting with a context empowered with all the tools necessary for the job that gets passed to the web controller as declared by the programmer, probably in some x-uses property found on the swagger.yaml
. It practically gets to be a sort of DI :smile:
mm. yea. I know - now the context is not passed to the web controller because the swagger_router passes (req,res,next)
. For this I have two options: start with a pipe that mounts the ctx on the req in some name, or replace swagger_router with a hacked version that passes (ctx, next) instead. I'm not sure which of them is cleaner.
The microservices must be slim: These tools must be cherry-picked into each project, where an initial boiler-plate will provide the barebones basics.
I don't want to wrap every adapter in a node-machine. We already have our own solution for documentation and exploration. We aspire to work with simple node-modules.
...
I'll appreciate any input.
Osher
You know what? Here's a rabbit hole I'd like your opinion about: Maybe we can explore it and get something good out of it. It will be fair if you say it's too soon for it, or too demanding for the schedule.
It's a rabbit hole because it rises few design discussions, but I believe it worth's exploring. Anyway:
Ideally, there will be an operation level in the spirit of x-pipe
attribute that describes a ...pipe! Not a controller-method...
This pipe should be a pipe defined add-hock for the operation. If I have to switch all the time between swagger.yaml
and default.yaml
- we lose the elegance.
So we may decide to allow be x-pipeon the path level that is **_extended_** with fittings from the
x-pipeon the opertion/verb level. We can even consider
x-pipeon the document level as a common start point to all
x-pipe`s, but this has to be flexible and easy to play with, or we lose our audience.
So what does it give us?
In this sense, as far as the web-context is concerned - it's made of 2 adaptation fittings:
The first extracts parameters from web-context request, and the last translates result (from ctx.output
?) into web-context response.
All the fittings in the middle - can be indifferent to the context and decoupled from it - They don't care if the pipe is operated in a web context, a CLI context, a msg-queue consumer - they can just do their work, and pass results.
any thoughts?
do I talk to much , or you really need time to conteplate all this?
I haven't had much of a chance to think about this, but we're already pushing the boundaries of what Swagger (Open API) defines (it should basically just be the REST interface), so I'm not sure it's wise to dive even further down this programming language rabbit hole inside the Open API document.
Note that Pipes and Fittings are already decoupled from web context. It's only the connect_middleware that knows how to translate the simple in/out of the pipe from a request and into a response.
that's not quite true. one big difference is:
the controller handlers are written for (req,res,next)
, and have no
access to the ctx
,
so if I want to collect augmented tools on ctx
to be used in the
controller handlers - I have to have a pipe fitting with a line like
ctx.request.ctx = ctx
or write the fitting to specifically augment ctx.request
both alternative feel wrong... do you get to see what I mean?
if operation handlers would get to be written for (ctx, next)
- then they
get to be just like any other fitting :)
The ctx will have .request
, and .response
, but they should not be meant
to use them.
They will for example - use ctx.myCoolDal.get(..., cb)
, use
ctx.myConf.get(..)
, use ctx.logger(..)
- maybe even ctx.params
, which
will reflect ctx.request.swagger.params
, and end with done(null, { ..body-data...} )
or maybe even collect all the data on ctx.result
which
done()
can then use.
Now this will truly decouple them from the web-context :)
On Mon, Sep 19, 2016 at 11:39 PM, Scott Ganyo notifications@github.com wrote:
I haven't had much of a chance to think about this, but we're already pushing the boundaries of what Swagger (Open API) defines (it should basically just be the REST interface), so I'm not sure it's wise to dive even further down this programming language rabbit hole inside the Open API document.
Note that Pipes and Fittings are already decoupled from web context. It's only the connect_middleware that knows how to translate the simple in/out of the pipe from a request and into a response.
β You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/theganyo/swagger-node-runner/issues/58#issuecomment-248118846, or mute the thread https://github.com/notifications/unsubscribe-auth/AAxBHWnloSdps1PwkjAQ1v6HAVAzPjTeks5qrvL1gaJpZM4J0gCx .
I'm still trying to understand your use-case and I think I'm failing, so forgive me if I over-simplify (or get it completely wrong), but what I'm gathering is that you'd like to do something like this:
swagger-node-runner -> pipe -> [middleware] -> pipe -> swagger-node-runner
Where you'd be plugging in some existing controllers that uses the standard (req, res, next) function as [middleware]. But you also don't want to wrap your controllers in fittings.
Am I completely off-base? Sorry if so... Maybe it would help me to understand if we were discussing one simple, concrete example.
mm. let me illustrate.
infra packates:
basically, all you need to keep your controllers slim, logical and free from implementation details And this is why I have to require fittings from node-modules
Decoupling user-code and infra pkgs mean that I can use them in any other context: msg-queue-consumer, TCP server - you name it. It looks like something that is more valuable for infra-pkg only, but for example - consider a design for scale, where POC implements logic in web context, and later replace web handler with msg-queue producer, and moves the data-crunching to msg-queue consumer. This is one case where the context is changed, and I would like the code not to change.
I really adore the idea of bagpipes, I actually started designing one for my own just before I discovered it inside swagger-node-runner, and realizing the potential of this - _my brain exploded!!_
I was so happy that there are other good people that think like me :smile:
mmm. there are still open questions re. how decoupled should the execution context and the web context be. For now I think that as an implementation detail, it will be the same context. It will be simpler to start like that.
But I foresee a stage where we'll have to really decouple them - maybe because of name collisions, maybe because of other reasons - but we'll have to be creating a private focused context for the execution. For this we'll have to decide how to indicate what tools that are initiated once in the router pipe = i.e. are put on the router web context, but should be passed to the execution context.
By when that time comes - I trust we'll have few projects running over this structure, and we'll figure the right balance from observing what we got.
P.S. I just found that some people answer comments right from the mail, where I often use the comments like an editor: I often "upgrade" my comments - simply because github allows it, and I don't know if github informs participants when a message is edited. I'll try not to do it, but those are very rooted habits, started years back when skype started allowing editing sent messages... :(
Anyway - bottom line - final messages on the github web.
im considering using my own router fitting - a lightly hacked version of the router of the runner - and avoiding the mehhhh...
on the bottom left
Fantastic! A picture is worth ~1k words! Thanks!
I (finally) think I understand where you're going. So to kind of summarize back into prose: Based on your desired state, you'd prefer a direct mapping to pipes you've built for the security and router fittings. Correct?
Let me think on this a bit...
yes - this summary is much more on target :) However - while you think of it - don't miss the following stresses
but lets start with the obvious:
I saw the x-swagger-pipe
directive, which you use to cascade the swagger_controller
pipe for serving the swagger document.
The problem with using it for custom controller pipes are:
So.
Having that said - we can distinct the following following levels:
In this stage I did not find a use-case for post-operation. post-path, post-all fitting, but I'm sure that if the need comes - we'll find how to feature it in an elegant way :wink: (well, that's not true, there's the request logger, and the response-time reporter - however I'm currently doing it streight over restify).
.
I'm happy you want to give it a thought. I'll be happy to be part of the solution - we could work on it together in a branch or something - it will be a delight
However - I noted the time differences between us. I'll be going for a holiday for 10 days, however, I'm traveling north, so the time-differences will stay as they are... I hope that my lowered availability will not break the dialog...
Cheers!!
Great!
First off, though, I want to make sure we set a baseline:
Assuming those two statements are correct, it seems like the scope of what we'd need to address for your use case could be reduced quite a bit.
That said, I think it might make sense to break the discussion apart into two general areas of potential improvement (or at least we should consider these as related, but not interdependent items):
Am I still missing something here?
I think we're quite on target!
I tried x-swagger-pipe
for an operation level - I did not get what I was expected. I did it for fun - and did not hinder on it because of that.
I'll double-check myself, and if reproduced - will create a bug for the matter.
Next - Ah - pipe composition is something I did not consider. This opens some possibilities.
Having a pipe per operation should reduce the back and forth between default.yaml
and swagger.yaml
but will not eliminate it completely. Assuming they are all composed with a reusable baseline - it does make things very bearable.
We can even add a feature-flag to allow deduction of custom pipe name by some straight-forward naming convention - this puts us almost there in this front, and lowering priority on the requirement to execute prior to the controller fitting named on the swagger.yaml
on the path and operation level.
About pt. 2 - I propose to support both functionalities side by side as passing to the controllers (ctx, done)
, or (ctx.request, ctx.response, next)
at the user's choice.
It can be an alternative router module, or a fitting configuration flag.
I think I can put up a PR with the configuration flag option, based on something in the spirit of:
in default.yaml
:
openapi_error:
name: json_error_handler
handle500Errors: true
openapi_cors:
name: cors-patch #just requires 'swagger-node-runner/fittings/cors'
openapi_validate:
name: swagger_validator
validateResponse: false
openapi_router:
name: swagger_router
controllersDirs: [ lib/web/controllers ]
controllersType: middleware # passes (req, res, next) - the default behavior
# OR
controllersType: pipe # passes (ctx, done)
# done = the layer that expects
# ctx.output and emits it to ctx.response
swagger_endpoint:
- onError: openapi_error
- openapi_cors
- swagger_params_parser
- swagger_security
- openapi_validate
- express_compatibility
- openapi_router
thoughts?
Hey! Sorry for the delay.
So, glad the composition sounds like it may work for you.
So... on part two... I wonder if this could happen automagically using, say, Function.length
so we wouldn't have to add configuration for it?
I'm still thinking about it, so lets express some thoughts in write, and consider if all of them is a desired effect, and see if it all makes sense.
The biggest implication I can point in this stage is:
Using the .length
of a function means that you could in fact have pipes written as a middleware for (ctx.req, ctx.res, next)
side by side with pipes written for (ctx, done)
- where in my original proposition all of them must be of the same type.
I see greater values for this especially if we open the possibilities to use pipes from the node_modules - i.e. - use as a pipe a package that exports a middleware function by naming that package name as a fitting in a compound pipe (and on the same breath we get for free support for packages that exports a mw-factory that accepts a sinlge config object π, if you see what I mean here). Currently what stops us is https://github.com/theganyo/swagger-node-runner/issues/58.
But.
This freedom comes in trade for adding a not-so-obvious gotcha
to an already under-communicated project.
This gotcha feels like a detail that will be missed in the docs/readme by many a developer - a hurdle that can be mitigated only to a limit.
It will also mean that there will not ever be any other future controller-type that accepts 3 arguments that is not a middleware, and no other future controller-type that will accept 2 arguments that is not made for (ctx, done)
(or we will have to introduce configs over this gotcha
).
However, since it's by definition a utilization for a web project - I suppose that we should be able to name this as a base assumption.
It also means that the returned functions will have to explicitly name their args to ensure they answer the .length
test, no tricks allowed. Despite not being too happy about it I tend to accept this limitation too.
If we're on the same page - I think we have a design π
Yup. I totally, agree. There are potential issues, but...
It's the "no tricks" thing is the one that gives me a bit of heartburn - it's hard to put restrictions on things you can't control (and the node_modules issue is actually a good example of this). But, it does feel like the flexibility gained by this could be worth it.
Roger. I would have started right away, but I have to catch up a gap with my professional tasks π’
So say we all.
I'll have a window for this, hopefully in a matter of days.
A colleague architect pointed my attention that many developers write just function(req,res)
whenever they don't intend to use the next
- and this gives more weight to the discussed gotcha, because their implementations would yield Function#length
will be 2, mistakenly taken for (ctx, next)
.
I suggest to support the following:
.length
to distinct between (req,res,next)
to (ctx,next)
config search stops on first find according to the following fallback chain:
Thoughts?
Oh curse those lazy developers! ;)
So... I'm going to reverse myself, and say that we should rely on the config annotations instead of auto-detect because it could be a breaking-change for some folks. (It's what I should've said in the first place anyway.)
That said.. if you'd still like that mix-and-match auto-detect feature, we could have that if we simply add an "auto" config option. So we'd have: "middleware", "pipe", and "auto"... currently, of course, it's always "middleware" and we continue to default to that so as not to break anything. "pipe" sets it to pipe, natch. And "auto" sets it to use its best judgement with .length... and it's up to the developer to ensure that things don't break. Do you think this is worthwhile?
The config search chain looks good.
I feel much safer with your new decision. That makes me happy π
Oups. Forgot something. fittingDef
.
So, update to config search. Still stops on first find. But following fallback chain is:
operation.definition['x-controller-interface']
operation.pathObject.definition['x-controller-interface']
operation.pathObject.api.definition['x-controller-interface']
fittingDef.controllersInterface
, 'middeware'
How would you suggest to handle illegal values?
We can validate point 4 during start-up. That's very easy. But I don't see an easy reference to the sway.SwaggerApi to preamptively validate all the rest on start up. Maybe I'm not looking in the right place?
If there is an easy reference to the sway.SwaggerApi
object that you can point me to use - then great.
I think I'll prefer to do it on start-up, and eliminate errors already on dev time.
Otherwise - I can offer the following, relaying on runtime behavior:
.definition
object)A more harsh approach is emit an error and passing an error to the callback.
What is it going to be?
mmm. Found it
bagpipes.config.swaggerNodeRunner.api
It will be on load time :)
In this case I can optimize and load each operation with it's final value instead of searching the value for every operation execution
I'm not sure where you are working, but in most places in swagger-node-runners's index.js, it's just self.api
.
The PR is not ready, but you can have an early look. Feedback is always appreciated :)
Ok. the PR is on a good state, although I'd rather do 2 more things there. please have a look.
I'm trying to create some reusable pipe fittings packed as simple npm packages.
So to prove the concept I placed a simple
node_modules/required-fitting/index.js
(together with a standardpackage.json
) in same folder, as follows:And added to the config:
So:
Alas - the pipe explodes. The error is observed on the response as
{"message":"undefined is not a function"}
, with NO MENTION of it on the server logIt appears to explode on
cors
, which is now ambiguous between the two paths:node_modules/cors
lib/fitting/cors.js
playing with order of paths in
fittingsDir
did not help :(I can PR to rename
lib/fitting/cors.js
tolib/fitting/swagger_cors.js
or anytihng of that sort, however, it looks like a breaking change.I think the situation points also to an additional problem: I think the fitting builder that calls the fitting factory may be required to make sure that the factory returns a function in the correct arity. (the vanilla
cors
package is af(1)
factory that returnsf(3)
)I did not miss the part about
type:node-machine
that requires some overhead plus pushes me to experimental zone, which I would totally prefer to avoid at this stage... my client wont approve of anything with experimental labelled on it...Thoughts?