Skip to content
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

requestOptions accepting function so that we can change options depending on the request #381

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

dinumathai
Copy link

@dinumathai dinumathai commented Mar 22, 2021

What is this change ?
Added urlResolver on Resolver options, so that we can change the URL for api call depending on the request context.

Why Is this change ?
To pull data from multiple api which is having the similar signature, depending on a value from the request header.

@Alan-Cha
Copy link
Collaborator

Alan-Cha commented Mar 22, 2021

@dinumathai Thank you for this contribution! I generally like the idea of this PR. Giving full control over the requests that the resolvers make will make OtG more usable and gives more options to users.

However, I wonder if we should in a different way. I will give some background. In the beginning, there was only the headers and qs options for controlling requests. Later on, we added requestOptions. requestOptions provides more functionality than headers and qs so are thinking of deprecating headers and qs.

Another user also requested the programmatically control request headers so we modified requestOptions so it can accept a function for the headers field.

I do not want to add new request options (I would like to deprecate headers and qs) but after seeing your PR, I think it makes more sense that we allow the entire requestOptions to be a function, instead of just headers, and this way users can also specify the url.

Before:

export type RequestHeadersFunction<TSource, TContext, TArgs> = (
  method: string,
  path: string,
  title: string,
  resolverParams?: {
    source: TSource
    args: TArgs
    context: TContext
    info: GraphQLResolveInfo
  }
) => Headers

export declare type RequestURLFunction<TSource, TContext, TArgs> = (
  method: string, 
  path: string, 
  title: string, 
  resolverParams?: {
    source: TSource;
    args: TArgs;
    context: TContext;
    info: GraphQLResolveInfo;
}) => string;

export type RequestOptions<TSource, TContext, TArgs> = Omit<
  NodeRequest.OptionsWithUrl,
  'headers'
> & {
  headers?: Headers | RequestHeadersFunction<TSource, TContext, TArgs>
}

// Has `headers`, `qs`, and `urlResolver`
// Has awkward `requestOptions` with modified `headers` field
export type InternalOptions<TSource, TContext, TArgs> = {
  ...
  headers?: Headers | RequestHeadersFunction<TSource, TContext, TArgs>
  qs?: { [key: string]: string }
  urlResolver?: RequestURLFunction<TSource, TContext, TArgs>
  requestOptions?: Partial<RequestOptions<TSource, TContext, TArgs>>
  ...
}

After:

export type RequestOptionsFunction<TSource, TContext, TArgs> = (
  method: string,
  path: string,
  title: string,
  resolverParams?: {
    source: TSource
    args: TArgs
    context: TContext
    info: GraphQLResolveInfo
  }
) => Partial<NodeRequest.OptionsWithUrl>

// Deprecated `headers` and `qs`
// requestOptions is an object or a function
// if `url` or `headers` is changed, then request will reflect those changes
export type InternalOptions<TSource, TContext, TArgs> = {
  ...
  requestOptions: Partial<NodeRequest.OptionsWithUrl> | RequestOptionsFunction<TSource, TContext, TArgs>
  ...
}

Does this make sense? What do you think?

@dinumathai
Copy link
Author

dinumathai commented Mar 23, 2021

That is a better solution. But do we need to keep qs and headers for backward compatibility ?

Also we need to think of replacing the deprecated package "request".

@Alan-Cha
Copy link
Collaborator

I am fine with making breaking changes as long as they are released in a major version and it is properly documented. However, I do have a few concerns about this change.


Currently, we perform an Object.assign() to the requestOptions (see here). If we want to give total control to the user, then we need to give a higher priority to requestOptions and even add the option to prevent OtG from changing it/for OtG to use the raw requestOptions.

Perhaps we should also consider adding a new option, like requestOptionsStrict that is a boolean that controls this. If it is set to false, we stick to the original behavior, Object.assign(), and this is useful if a user just wants to use a new authentication header but does not want to change anything else. If it is set to true, then we use the raw requestOptions (the object or the output of the function), and this will give more confidence to users who want to have total control over everything.

I'm concerned that there may be a third case. The url is also a field under requestOptionsStrict. I wonder if it may need special treatment. For example, there may be occasions where we want to strictly apply all of requestOptions but not url as well as occasions where we want to strictly apply all of requestOptions. Maybe we can make it so that if the user wants to strictly apply requestOptions and url is not provided, then we default to whatever OtG comes up with (as url is a required field).

I also wonder if we should add a new option like requestOptionsStrict or make requestOptions an object that also contains a strict field. I feel like the latter might be more intrusive to use.


Currently, we are using the request module to perform our API calls. However, the module has been long deprecated and we need to use a new library. There is a PR for it #315 (as well as related #268).

Until we find a solution for this, I hesitate to make very drastic changes. Therefore, I do not think we should remove headers and qs just yet. I still think that making requestOptions is still a useful feature until we finally switch libraries.


What do you think?

@dinumathai
Copy link
Author

Just wanted to put one more option for consideration.

Shall we pass "options" as a parameter to the RequestOptionsFunction. This way developer will have more control. But we will need to validate for url and method in options.

export type RequestOptionsFunction<TSource, TContext, TArgs> = (
  options: Partial<NodeRequest.OptionsWithUrl>,
  method: string,
  path: string,
  title: string,
  resolverParams?: {
    source: TSource
    args: TArgs
    context: TContext
    info: GraphQLResolveInfo
  }
) => void

@dinumathai
Copy link
Author

dinumathai commented Mar 24, 2021

@Alan-Cha, Added RequestOptionsFunction. Please review, and let me know the changes needed.

Feel free to make any changes.

@dinumathai dinumathai changed the title fix: Added urlResolver on Resolver options Changed requestOptions as function so that we can change options depending on the request Mar 24, 2021
@dinumathai dinumathai changed the title Changed requestOptions as function so that we can change options depending on the request requestOptions accepting function so that we can change options depending on the request Mar 24, 2021
…ding on the request

Signed-off-by: Dinu Mathai <Dinu.Mathai1@T-Mobile.com>
Signed-off-by: Alan Cha <Alan.cha1@ibm.com>
@Alan-Cha
Copy link
Collaborator

Shall we pass "options" as a parameter to the RequestOptionsFunction.

Yes, this is a great idea. I was also thinking of this.


@dinumathai I made a large number of changes, including restructuring the resolver_builder.ts to make it clearer and cleaner.

There is a complication that has come up that I wasn't aware of. This is a note that I have added to the README.md regarding the new requestOptions.

If used as a function, this will be used to modify resolvers AND the schema. For example, if a foo header is set, then the resolver call will append the foo header, and the foo parameter will be removed from the GraphQL schema. In resolver_builder.ts, the function will be called with all arguments, but in schema_builder.ts, only the method, path, and title will be provided (because resolverParams and generatedRequestOptions are only available during execution). Therefore, for query, path, header, and cookie parameters, a value must be provided without resolverParam and generatedRequestOptions or else the schema will not have the right parameters.

First, let me know if this makes any sense to you. Secondly, I just want to point out that this has always been a problem, for example with using the headers option as a function. It was just never documented properly. Thirdly, I'm not very happy with this solution. This seems to force the user to do some unnatural things if he/she wants to use resolverParams and generatedRequestOptions.

Maybe there should be two different functions, one that is used for resolver_builder.ts and get all of the parameters and produces the request options, and one used for schema_builder.ts that gets only method, path, and title and returns true/false if certain arguments should be skipped in the schema.

I hope this all makes sense. If it's not intuitive, that is exactly why this is a big issue.

@@ -11,26 +11,31 @@ import { RequestOptions } from './types/options';
import { GraphQLFieldResolver } from 'graphql';
import { IncomingHttpHeaders } from 'http';
export declare const OPENAPI_TO_GRAPHQL = "_openAPIToGraphQL";
declare type GetResolverParams<TSource, TContext, TArgs> = {
declare type GetSubscribeParams<TSource, TContext, TArgs> = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just cleaning up old code. Just ignore.

@@ -97,7 +131,7 @@ export declare type InternalOptions<TSource, TContext, TArgs> = {
* into a GraphQL ID type. To allow for more customzation, this option allows
* users to specify other formats that should be interpreted as ID types.
*/
idFormats?: string[];
idFormats: string[];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More clean up.

}
}
// The options `requestOptions` will finalize the options
if (requestOptions) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requestOptions has been moved to the very bottom, right before the call. This allows requestOptions to have the final say on how the request should be made.

Signed-off-by: Alan Cha <Alan.cha1@ibm.com>
path,
title,
{ source, args, context, info },
options
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where we also provide the generated Request options as you suggested.

} else {
const oauthHeader = createOAuthHeader(data, context)
Object.assign(options.headers, oauthHeader)
options = merge(options, requestOptionsValue)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice that we merge the original options with requestOptionsValue. If we were to implement a requestOptionsStrict option like I mentioned previously, if would do something here.

* provided through the options
*/
function skipArg<TSource, TContext, TArgs>(
function shouldSkipArgumentBecauseOptions<TSource, TContext, TArgs>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where the problem I mentioned before comes from.

getResolver in resolver_builder.ts uses requestOptions to change the API call.

shouldSkipArgumentBecauseOptions in schema_builder.ts uses requestOptions to determine if it should add an argument to the field in the GraphQL

They both provide different arguments.

*/
const requestOptionsValue =
typeof requestOptions === 'function'
? requestOptions(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we provide method, path, title, resolver arguments, and generated options

typeof data.options.requestOptions === 'object'
? data.options.requestOptions
: typeof data.options.requestOptions === 'function'
? data.options.requestOptions(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we only provide method, path, and title. No resolver arguments or generated request options.

headers: (method, path, title) => {
if (method === 'get' && path === '/snack') {
return {
requestOptions: (method, path, title) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example of using requestOptions as a function.

Copy link
Author

@dinumathai dinumathai Mar 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Thank you.

@dinumathai
Copy link
Author

Planned any date for merge ?

@Alan-Cha
Copy link
Collaborator

Alan-Cha commented Apr 6, 2021

@dinumathai I have a lot of concerns about whether this is the right design. As I mentioned previously, requestOptions is used by both the resolver_builder.ts and schema_builder.ts and they provide different arguments. Do you have any opinions on this?

@dinumathai
Copy link
Author

dinumathai commented Apr 7, 2021

@dinumathai I have a lot of concerns about whether this is the right design. As I mentioned previously, requestOptions is used by both the resolver_builder.ts and schema_builder.ts and they provide different arguments. Do you have any opinions on this?

Only solution I can think of is - Update README - explaining about this.

@dinumathai
Copy link
Author

Or shall we move back to initial option of adding new parameter ?

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

1 similar comment
@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

16 similar comments
@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@ibm-ci-bot
Copy link

@dinumathai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

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.

3 participants