Skip to content

Add facility to intercept requests and responses in host application. #434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from

Conversation

richgoldmd
Copy link

@richgoldmd richgoldmd commented Nov 19, 2020

Often times when implementing an API client, the need arises to intercept the the outgoing request or incoming response, and act on it in a way that may be globally applied or may not be anticipated in a specific API.

For example, a server may return status information unrelated to the specific api call about session status. A client application could monitor every api call for the potential exception that may indicate such a status, but then it needs to be handled in every instance. This PR enables the addition global hooks before a request is made, and after a response is received (but before it is checked for errors).

This is accomplished by providing optional callbacks in the OpenAPI object:

export interface RequestHookParams {
    url: string;
    options: ApiRequestOptions;
}

export interface ResponseHookParams {
    url: string;
    result: ApiResult;
    response?: any;
}

type Config = {
    BASE: string;
    VERSION: string;
    WITH_CREDENTIALS: boolean;
    REQUEST_HOOK?(params: RequestHookParams): Promise<RequestHookParams>;
    RESPONSE_HOOK?(result: ResponseHookParams): Promise<ApiResult>
    TOKEN?: string | Resolver<string>;
    USERNAME?: string | Resolver<string>;
    PASSWORD?: string | Resolver<string>;
    HEADERS?: Headers | Resolver<Headers>;
}

The request hook, if provided, is called with the url and ApiRequestOptions and returns the same parameters which may have been altered by the hook.

Similarly, the response hook is called with the url, ApiResult, and the actual response object from the underlying http implementation, and returns a potentially altered ApiResult (e.g. a known error condition which has been handled can be reset to 'ok' by returning a status 200 and ok: true). The implementation-specific response object is passed so that it can be queried as needed in the response hook (in my use case to scan for one or more application-specific headers).

Here is a sample response hook implementation which detects and handles a specific header and resets the result to prevent an additional, unneeded exception being raised:

OpenAPI.RESPONSE_HOOK = async r => {
  const result = r.result;
  const status = (r.response as Response).headers.get("X-Session-Status");
  if (status == "session-terminated") {
     // Do something here to handle this scenario

    // Massage result as this error is handled
    return {
      url: result.url,
      ok: true,
      status: 200,
      statusText: "OK",
      body: null
    };
  }
 
  return result;
};

@codecov
Copy link

codecov bot commented Nov 19, 2020

Codecov Report

Merging #434 (866f8ef) into master (1830f73) will increase coverage by 0.02%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #434      +/-   ##
==========================================
+ Coverage   93.49%   93.51%   +0.02%     
==========================================
  Files         106      106              
  Lines        1260     1264       +4     
  Branches      219      219              
==========================================
+ Hits         1178     1182       +4     
  Misses         82       82              
Impacted Files Coverage Δ
src/utils/registerHandlebarTemplates.ts 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1830f73...866f8ef. Read the comment docs.

@markygab
Copy link

This is exactly the extension to the project that we also need to implement some custom auth. Thank you @richgoldmd for taking the time to do this.

@richgoldmd
Copy link
Author

@markygab I updated this PR to define the Response type as an implementation-specific type:

export interface ResponseHookParams {
    url: string;
    result: ApiResult;
    response?: ResponseImplementation;
}

ResponseImplementation is assigned the type appropriate for the client implementation.

@ferdikoomen
Copy link
Owner

@richgoldmd I 'm working on a @next version that will have a 'bundler' this will load all the external refs before parsing this. In the meantime you can use something like https://github.com/APIDevTools/json-schema-ref-parser to load your spec and combine all references. After this you can send that whole spec to the generator. I will push a @next branch soon so we can add some test cases for your setup.

@richgoldmd
Copy link
Author

This PR is about hooking the http request/response flow - not the schema references.

@richgoldmd I 'm working on a @next version that will have a 'bundler' this will load all the external refs before parsing this. In the meantime you can use something like https://github.com/APIDevTools/json-schema-ref-parser to load your spec and combine all references. After this you can send that whole spec to the generator. I will push a @next branch soon so we can add some test cases for your setup.

@hampusborgos
Copy link

hampusborgos commented Jan 28, 2021

This extension is sorely needed to make the generated code more general in use.

A possible extension, would be if you could pass a context?: any parameter to all requests (as the last parameter), that would be passed to the callback(s) so you could pass request context as well.

Implemented here: Cancer-Expert-Now#1

@ehvattum
Copy link

This PR seems to implement a common pattern that is universally useful and powerful.

@ferdikoomen
Copy link
Owner

@ehvattum did you check the comments here: #465 You can create your own request method that allows all the flexibilty (for now).

@sjoerdmulder
Copy link

This PR has still value IMHO, creating your own request method is quite cumbersome maintainance wise, having those hooks would add all the extension points you need while still benefititng from possible upstream fixes.

Next it's backwards compatible and doesn't add a lot of complication.

@ferdikoomen
Copy link
Owner

@richgoldmd I'm ditching this PR in favour of #822 Yes it's more cumbersome to maintain your own client, but it the cleanest approach. One of the roadmap goals is also to get rid of the global OpenAPI object, this would be adding more tech debt in the future.

@ferdikoomen ferdikoomen closed this Dec 2, 2021
@richgoldmd
Copy link
Author

Thanks @ferdikoomen - I appreciate your efforts on this library. I agree that PR is an elegant solution.
I'm happy to continue to contribute to this project in any case - thanks again for the effort. 👍

@ferdikoomen
Copy link
Owner

@richgoldmd Thanks for the help so far! It's great to see the project being used outside some of my own projects. I really should spend some more time on this (specifically roadmap and documentation) so its easier to contribute.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants