openapi-ts icon indicating copy to clipboard operation
openapi-ts copied to clipboard

Content type headers not passed onto request

Open wizzyto12 opened this issue 1 year ago • 4 comments

Hello. We have a nextjs project with api-platform as backend and was trying out the generated client from openapi v3.1. I have noticed that content type is not parsed from the schema and resulting in the use of crazy logic in the interceptors (we need ld+json and merge+patch, which is all described in json). I have tried several different generators and they seem to respect it.

Also, its not very clear how the TOKEN works, as I cant quite make it work. Putting my token in header from interceptors does the trick. I really enjoy using this, but since its on an early stage, examples are old and docs are alim, I thought i'd ask and potentially help others looking for this.

Thanks for this great project!

wizzyto12 avatar May 15 '24 19:05 wizzyto12

Thanks @wizzyto12! Are you able to share the spec and expected results where it fails? Might be easier for me to fix content types that way

mrlubos avatar May 15 '24 19:05 mrlubos

Hey!

So this is my interceptors

OpenAPI.interceptors.request.use(async (request) => {
  request.headers = {
    ...request.headers,
    Accept: "application/ld+json",
    "Content-Type":
      request.method === "PATCH"
        ? "application/merge-patch+json"
        : "application/ld+json",
  };

  // Here I suppose that this can probably set the token without me adding it to the header manually. I also tried setting it as a string.
  // OpenAPI.TOKEN = async () => {
  //   const session = await auth();
  //
  //   return session.user.access_token;
  // };

  const session = await auth();

  // This is what works, but its obvious it will.
  if (session?.user?.access_token) {
    request.headers = {
      ...request.headers,
      Authorization: Bearer ${session.user.access_token},
    };
  }

  return request;
});

This is an entry from my openapi json file, which is openapi v3.1

"\/api\/booking_sessions": {
      "post": {
        "operationId": "api_booking_sessions_post",
        "tags": [
          "BookingSession"
        ],
        "responses": {
          "201": {
            "description": "BookingSession resource created",
            "content": {
              "application\/ld+json": {
                "schema": {
                  "$ref": "#\/components\/schemas\/BookingSession.jsonld-thing.get"
                }
              }
            },
            "links": {}
          },
          "400": {
            "description": "Invalid input"
          },
          "422": {
            "description": "Unprocessable entity"
          }
        },
        "summary": "Creates a BookingSession resource.",
        "description": "Creates a BookingSession resource.",
        "parameters": [],
        "requestBody": {
          "description": "The new BookingSession resource",
          "content": {
            "application\/ld+json": {
              "schema": {
                "$ref": "#\/components\/schemas\/BookingSession.jsonld-booking-session.post_thing.post"
              }
            }
          },
          "required": true
        },
        "deprecated": false
      },
      "parameters": []
    },

We can see that it has the appropriate "application/ld+json", which is not respected, or I don't know how to make it work.

Usage in my code, random example

async function login(credentials: { email: string; password: string }) {
  const response = await LoginCheckService.appAuthPost({
    requestBody: credentials,
  });

  if (response.token) {
    const decodedToken = jwtDecode(response.token) as DecodedToken;

    return { .... } as User;
  }

  return null;
}

I get a 406 from my API if the content is not set in the interceptors accordingly. If thats not clear enough (I know its all around the place with the codes, but still), I can create an MVP example of api platform and nextjs to try to recreate the issue in a clean project.

Thanks for the fast reply!

wizzyto12 avatar May 15 '24 20:05 wizzyto12

That should be plenty, thank you!

mrlubos avatar May 15 '24 20:05 mrlubos

@mrlubos In case this is helpful, I have debugged it and turn out the reason the header is not attached is because the header constructor, it don't get the object passed in. As the code below, the first console.log (label headers 164) have enough headers property (including content-type), but the second log is empty.

For now, I will use interceptor and set content-type manually to work around it.

export const getHeaders = async (
	config: OpenAPIConfig,
	options: ApiRequestOptions
): Promise<Headers> => {
	const [token, username, password, additionalHeaders] = await Promise.all([
		resolve(options, config.TOKEN),
		resolve(options, config.USERNAME),
		resolve(options, config.PASSWORD),
		resolve(options, config.HEADERS),
	]);

	const headers = Object.entries({
		Accept: 'application/json',
		...additionalHeaders,
		...options.headers,
	})
		.filter(([, value]) => value !== undefined && value !== null)
		.reduce(
			(headers, [key, value]) => ({
				...headers,
				[key]: String(value),
			}),
			{} as Record<string, string>
		);

	if (isStringWithValue(token)) {
		headers['Authorization'] = `Bearer ${token}`;
	}

	if (isStringWithValue(username) && isStringWithValue(password)) {
		const credentials = base64(`${username}:${password}`);
		headers['Authorization'] = `Basic ${credentials}`;
	}

	if (options.body !== undefined) {
		if (options.mediaType) {
			headers['Content-Type'] = options.mediaType;
		} else if (isBlob(options.body)) {
			headers['Content-Type'] = options.body.type || 'application/octet-stream';
		} else if (isString(options.body)) {
			headers['Content-Type'] = 'text/plain';
		} else if (!isFormData(options.body)) {
			headers['Content-Type'] = 'application/json';
		}
	}

  console.log('headers 164', headers)
  console.log('new Headers(headers)', new Headers(headers))

	return new Headers(headers);
};

vvtri avatar May 20 '24 09:05 vvtri

Hey, I am going to close this issue as it pertains to legacy clients. These are now in maintenance mode and I am open to pull requests, so if anyone sees this and would like to fix it, please open a pull request. The recommended approach moving forward is to use standalone clients that do not suffer from this type of problems.

mrlubos avatar Aug 11 '24 11:08 mrlubos