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

Generating a client for an endpoint that downloads binary data #3855

Open
kyleratti opened this issue Dec 5, 2023 · 15 comments
Open

Generating a client for an endpoint that downloads binary data #3855

kyleratti opened this issue Dec 5, 2023 · 15 comments
Assignees
Labels
generator Issues or improvements relater to generation capabilities. help wanted Issue caused by core project dependency modules or library type:bug A broken experience
Milestone

Comments

@kyleratti
Copy link

kyleratti commented Dec 5, 2023

I have an OpenApi endpoint that serves binary data (e.g., image/jpeg, image/png, image/gif, etc.) I am using Kiota to generate a C# API client. However, it seems that the generated client does not support endpoints that serve binary (OpenApi schema type file, format binary.)

As far as I can tell, this is the correct way to describe the endpoint. Is it possible to have Kiota generate a client function that can call this endpoint and return either a Stream (preferred) or a byte[]? I see vague mentions of defaulting to Stream for unknown mimetypes on #1471, however that is not the behavior I am seeing now.

I do see a warning about this when generating my client which makes me think my API description may be wrong?

The format binary is not supported by Kiota for the type binary and the string type will be used.

Here's the signature of the generated function:

public async Task<string?> GetAsync(Action<BytesRequestBuilderGetRequestConfiguration>? requestConfiguration = default, CancellationToken cancellationToken = default)

And here's my OpenApi Schema:

{
  "openapi": "3.0.1",
  "info": {
    "title": "WebApi",
    "version": "v1"
  },
  "paths": {
    "/image/{imageId}/bytes": {
      "get": {
        "tags": [
          "Image"
        ],
        "parameters": [
          {
            "name": "imageId",
            "in": "path",
            "required": true,
            "style": "simple",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "maxWidth",
            "in": "query",
            "style": "form",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "maxHeight",
            "in": "query",
            "style": "form",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "cropImage",
            "in": "query",
            "style": "form",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "file",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "file",
                  "format": "binary"
                }
              },
              "image/gif": {
                "schema": {
                  "type": "file",
                  "format": "binary"
                }
              },
              "image/bmp": {
                "schema": {
                  "type": "file",
                  "format": "binary"
                }
              },
              "application/octet-stream": {
                "schema": {
                  "type": "file",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ImageNotFoundResult"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageNotFoundResult"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageNotFoundResult"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ImageNotFoundResult": {
        "type": "object",
        "properties": {
          "imageId": {
            "type": "string",
            "nullable": true
          },
          "message": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      }
    },
    "securitySchemes": {}
  },
  "security": []
}

This snag aside, I do want to say this is a fantastic tool and I'm very appreciative of all of the contributors!

@github-project-automation github-project-automation bot moved this to Todo in Kiota Dec 5, 2023
@baywet baywet self-assigned this Dec 5, 2023
@baywet baywet added question generator Issues or improvements relater to generation capabilities. labels Dec 5, 2023
@baywet baywet added this to the Backlog milestone Dec 5, 2023
@baywet
Copy link
Member

baywet commented Dec 5, 2023

Hi @kyleratti ,
Thanks for using kiota and for reaching out.
According to the OAS format registry the type should be string, and not file here. Can you try adjusting the description and see whether you still run into the issue please?

@kyleratti
Copy link
Author

Hey @baywet,

Thanks for your speedy response! Unfortunately, a type: "string" and format: "binary" still produces the same Task<string?> signature:

public async Task<string?> GetAsync(Action<BytesRequestBuilderGetRequestConfiguration>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
  "openapi": "3.0.1",
  "info": {
    "title": "WebApi",
    "version": "v1"
  },
  "paths": {
    "/image/{imageId}/bytes": {
      "get": {
        "tags": [
          "Image"
        ],
        "parameters": [
          {
            "name": "imageId",
            "in": "path",
            "required": true,
            "style": "simple",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "maxWidth",
            "in": "query",
            "style": "form",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "maxHeight",
            "in": "query",
            "style": "form",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "cropImage",
            "in": "query",
            "style": "form",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ImageNotFoundResult"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageNotFoundResult"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageNotFoundResult"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ImageNotFoundResult": {
        "type": "object",
        "properties": {
          "imageId": {
            "type": "string",
            "nullable": true
          },
          "message": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      }
    },
    "securitySchemes": {}
  },
  "security": []
}

@baywet
Copy link
Member

baywet commented Dec 5, 2023

Thanks for the additional information.
Out of curiosity, what happens if you remove the schema all together and only keep "application/octet-stream: {}" ?

@kyleratti
Copy link
Author

That's an interesting question! Unfortunately it looks like it still ends up with a Task<string> signature on the GetAsync function:

{
  "openapi": "3.0.1",
  "info": {
    "title": "WebApi",
    "version": "v1"
  },
  "paths": {
    "/image/{imageId}/bytes": {
      "get": {
        "tags": [
          "Image"
        ],
        "parameters": [
          {
            "name": "imageId",
            "in": "path",
            "required": true,
            "style": "simple",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "maxWidth",
            "in": "query",
            "style": "form",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "maxHeight",
            "in": "query",
            "style": "form",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "cropImage",
            "in": "query",
            "style": "form",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/octet-stream": { }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ImageNotFoundResult"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageNotFoundResult"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageNotFoundResult"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ImageNotFoundResult": {
        "type": "object",
        "properties": {
          "imageId": {
            "type": "string",
            "nullable": true
          },
          "message": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      }
    },
    "securitySchemes": {}
  },
  "security": []
}

@baywet
Copy link
Member

baywet commented Dec 5, 2023

This is strange since this is essentially what this unit test is testing for.
https://github.com/microsoft/kiota/blob/faf2b7a2669e51d2ca9eb35b3a39ed4cecd8aa9f/tests/Kiota.Builder.Tests/ContentTypeMappingTests.cs#L59C80-L59C80

Can you confirm which version of kiota you are using?
(kiota --version)

Can you try to tweak the unit test into making it fail?

@kyleratti
Copy link
Author

Strange indeed!

Here's my version output:

> kiota --version
1.8.2+212af8870aec683004cecc17f1943c7a247bcfa7

I'll try tweaking the unit test to see if I can get it to fail. I'll let you know what I find out!

@kyleratti
Copy link
Author

kyleratti commented Dec 5, 2023

Something hinky is definitely going on here. I'm still digging, but it looks like the C# code method writer will handle "binary" correctly (in CSharpConventionService.TranslateTypeAndAvoidUsingNamespaceSegmentNames) as it will generate a method with the return type of Task<byte[]> or Task<byte[]?>, although it doesn't look like there's test coverage on this case or int64.

This makes me think the issue is higher up the stack (which I think you are suspecting as well.)

I'm still digging but figured I'd drop this incase it gave you any more ideas.

@kyleratti
Copy link
Author

kyleratti commented Dec 5, 2023

I figured it out! This happens because my API spec has a 200 status code that returns binary data but the 400 status code returns JSON! If I remove the 400 status code from the spec, the client generator does indeed generate code giving me a Task<Stream?>!

I'm not sure if this is intentional or not? I have to imagine this is a difficulty with having different response types and models. Apologies if this is documented somewhere and I've just totally missed it. I appreciate your time!

@sebastienlevert
Copy link
Contributor

This would probably relate to this open issue we have: #2317

@baywet, any thoughts on this specific or 400s are also affected by that? Especially in this use case, it make a ton of sense to support different media types.

@baywet
Copy link
Member

baywet commented Dec 6, 2023

@kyleratti thanks for investigating this further. No it's not intentional, the return value is not supposed to be impacted by the error responses. Is this something that'd you like to submit a pull request for now that you have the unit test, I could give you pointers in the code to where this is being done.

@sebastienlevert I believe this is orthogonal.

@baywet baywet added type:bug A broken experience and removed question Needs: Attention 👋 labels Dec 6, 2023
@baywet baywet added the help wanted Issue caused by core project dependency modules or library label Dec 6, 2023
@baywet baywet assigned kyleratti and unassigned baywet Dec 6, 2023
@baywet baywet modified the milestones: Backlog, Kiota v1.10 Dec 6, 2023
@kyleratti
Copy link
Author

@baywet sure, I can give it a go! Pointers would definitely be appreciated though 😀

@baywet
Copy link
Member

baywet commented Dec 6, 2023

Awesome!
Setting a breakpoint here while debugging the unit test should get you close to finding out the root cause.

@baywet baywet modified the milestones: Kiota v1.10, Kiota v1.11 Jan 11, 2024
@baywet baywet modified the milestones: Kiota v1.11, Kiota v1.12 Jan 30, 2024
@sebastienlevert
Copy link
Contributor

@kyleratti is this something you are still interested in contributing? Let us know and we'll support / adjust accordingly! Thanks!

@kyleratti
Copy link
Author

kyleratti commented Feb 1, 2024

Hey @sebastienlevert! I am so sorry for the delay. The holidays really got away from me.

I am still interested in taking a stab at this issue. I can prioritize putting some time to it over the next few weeks if that's still OK?

@sebastienlevert
Copy link
Contributor

Absolutely! Thanks for your participation in this!

@petrhollayms petrhollayms modified the milestones: Kiota v1.12, Backlog Feb 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
generator Issues or improvements relater to generation capabilities. help wanted Issue caused by core project dependency modules or library type:bug A broken experience
Projects
Status: New📃
Development

No branches or pull requests

4 participants