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

(feat, csharp): support sending bytes requests #3901

Merged
merged 3 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 44 additions & 24 deletions generators/csharp/codegen/src/asIs/RawClient.Template.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ public RawClient(Dictionary<String, String> headers, ClientOptions clientOptions
_headers = headers;
}

public async Task<ApiResponse> MakeRequestAsync(ApiRequest request)
public async Task<ApiResponse> MakeRequestAsync(BaseApiRequest request)
{
var httpRequest = new HttpRequestMessage(request.Method, this.BuildUrl(request.Path, request.Query));
var url = this.BuildUrl(request.Path, request.Query);
var httpRequest = new HttpRequestMessage(
request.Method,
url
);
if (request.ContentType != null)
{
request.Headers.Add("Content-Type", request.ContentType);
Expand All @@ -44,44 +48,62 @@ public async Task<ApiResponse> MakeRequestAsync(ApiRequest request)
httpRequest.Headers.Add(key, value);
}
// Add the request body to the request
if (request.Body != null)
if (request is JsonApiRequest jsonRequest)
{
var serializerOptions = new JsonSerializerOptions
if (jsonRequest.Body != null)
{
WriteIndented = true,
};
httpRequest.Content = new StringContent(
JsonSerializer.Serialize(request.Body, serializerOptions), Encoding.UTF8, "application/json");
var serializerOptions = new JsonSerializerOptions { WriteIndented = true, };
httpRequest.Content = new StringContent(
JsonSerializer.Serialize(jsonRequest.Body, serializerOptions),
Encoding.UTF8,
"application/json"
);
}
}
else if (request is StreamApiRequest streamRequest)
{
if (streamRequest.Body != null)
{
httpRequest.Content = new StreamContent(streamRequest.Body);
}
}
// Send the request
HttpResponseMessage response = await _clientOptions.HttpClient.SendAsync(httpRequest);
return new ApiResponse
{
StatusCode = (int)response.StatusCode,
Raw = response
};
return new ApiResponse { StatusCode = (int)response.StatusCode, Raw = response };
}

/// <summary>
/// The request object to be sent to the API.
/// </summary>
public class ApiRequest

public abstract class BaseApiRequest
{
public HttpMethod Method;

public string Path;

public string? ContentType = null;

public object? Body { get; init; } = null;

public Dictionary<string, object> Query { get; init; } = new();

public Dictionary<string, string> Headers { get; init; } = new();

public object RequestOptions { get; init; }
}

/// <summary>
/// The request object to be sent for streaming uploads.
/// </summary>
public class StreamApiRequest : BaseApiRequest
{
public Stream? Body { get; init; } = null;
}

/// <summary>
/// The request object to be sent for JSON APIs.
/// </summary>
public class JsonApiRequest : BaseApiRequest
{
public object? Body { get; init; } = null;
}

/// <summary>
/// The response object returned from the API.
/// </summary>
Expand All @@ -92,7 +114,7 @@ public class ApiResponse
public HttpResponseMessage Raw;
}

private Dictionary<string, string> GetHeaders(ApiRequest request)
private Dictionary<string, string> GetHeaders(BaseApiRequest request)
{
var headers = new Dictionary<string, string>();
foreach (var (key, value) in request.Headers)
Expand All @@ -106,11 +128,9 @@ private Dictionary<string, string> GetHeaders(ApiRequest request)
return headers;
}

private string BuildUrl(string path, Dictionary<string, object> query)
public string BuildUrl(string path, Dictionary<string, object> query)
{
uri1 = _clientOptions.BaseUrl.TrimEnd('/');
uri2 = path.TrimStart('/');
var url = $"{uri1}/{uri2}";
var url = $"{_clientOptions.BaseUrl}{path}";
if (query.Count > 0)
{
url += "?";
Expand Down
7 changes: 6 additions & 1 deletion generators/csharp/sdk/src/endpoint/EndpointGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export class EndpointGenerator {
writer.write(`var ${RESPONSE_VARIABLE_NAME} = `);
writer.writeNodeStatement(
this.rawClient.makeRequest({
requestType: request?.getRequestType(),
clientReference: rawClientReference,
endpoint,
bodyReference: requestBodyCodeBlock?.requestBodyReference,
Expand Down Expand Up @@ -114,7 +115,11 @@ export class EndpointGenerator {
const parameters: csharp.Parameter[] = [];
const service = this.context.getHttpServiceOrThrow(serviceId);
const pathParameterReferences: Record<string, string> = {};
for (const pathParam of [...service.pathParameters, ...endpoint.pathParameters]) {
for (const pathParam of [
...this.context.ir.pathParameters,
...service.pathParameters,
...endpoint.pathParameters
]) {
const parameterName = pathParam.name.camelCase.safeName;
pathParameterReferences[pathParam.name.originalName] = parameterName;
parameters.push(
Expand Down
22 changes: 18 additions & 4 deletions generators/csharp/sdk/src/endpoint/RawClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { HttpEndpoint, HttpMethod } from "@fern-fern/ir-sdk/api";
import { SdkGeneratorContext } from "../SdkGeneratorContext";

export declare namespace RawClient {
export type RequestBodyType = "json" | "bytes";

export interface MakeRequestArgs {
/** the reference to the client */
clientReference: string;
Expand All @@ -16,6 +18,8 @@ export declare namespace RawClient {
headerBagReference?: string;
/** the query parameters to pass to the endpoint */
queryBagReference?: string;
/** the request type, defaults to Json if none */
requestType: RequestBodyType | undefined;
}
}

Expand All @@ -38,7 +42,8 @@ export class RawClient {
clientReference,
pathParameterReferences,
headerBagReference,
queryBagReference
queryBagReference,
requestType
}: RawClient.MakeRequestArgs): csharp.MethodInvocation {
const arguments_: csharp.ClassInstantiation.Arguments = [
{
Expand Down Expand Up @@ -73,13 +78,22 @@ export class RawClient {
assignment: csharp.codeblock(headerBagReference)
});
}
const apiRequest = csharp.instantiateClass({
let apiRequest = csharp.instantiateClass({
arguments_,
classReference: csharp.classReference({
name: "RawClient.ApiRequest",
name: "RawClient.JsonApiRequest",
namespace: this.context.getCoreNamespace()
})
});
if (requestType === "bytes") {
apiRequest = csharp.instantiateClass({
arguments_,
classReference: csharp.classReference({
name: "RawClient.StreamApiRequest",
namespace: this.context.getCoreNamespace()
})
});
}
return csharp.invokeMethod({
arguments_: [apiRequest],
method: "MakeRequestAsync",
Expand Down Expand Up @@ -124,7 +138,7 @@ export class RawClient {
const reference = pathParameterReferences[part.pathParameter];
if (reference == null) {
throw new Error(
`Failed to find request parameter for the endpointt${endpoint.id} with path parameter ${part.pathParameter}`
`Failed to find request parameter for the endpoint ${endpoint.id} with path parameter ${part.pathParameter}`
);
}
path += `{${reference}}${part.tail}`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { csharp } from "@fern-api/csharp-codegen";
import { HttpEndpoint, SdkRequest } from "@fern-fern/ir-sdk/api";
import { SdkGeneratorContext } from "../../SdkGeneratorContext";
import { RawClient } from "../RawClient";
import {
EndpointRequest,
HeaderParameterCodeBlock,
QueryParameterCodeBlock,
RequestBodyCodeBlock
} from "./EndpointRequest";

export class BytesOnlyEndpointRequest extends EndpointRequest {
public constructor(context: SdkGeneratorContext, sdkRequest: SdkRequest, endpoint: HttpEndpoint) {
super(context, sdkRequest, endpoint);
}

public getParameterType(): csharp.Type {
return csharp.Type.coreClass(
csharp.coreClassReference({
name: "Stream"
})
);
}

public getQueryParameterCodeBlock(): QueryParameterCodeBlock | undefined {
return undefined;
}

public getHeaderParameterCodeBlock(): HeaderParameterCodeBlock | undefined {
return undefined;
}

public getRequestBodyCodeBlock(): RequestBodyCodeBlock | undefined {
return {
requestBodyReference: this.getParameterName()
};
}

public getRequestType(): RawClient.RequestBodyType | undefined {
return "bytes";
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { csharp } from "@fern-api/csharp-codegen";
import { HttpEndpoint, SdkRequest, ServiceId } from "@fern-fern/ir-sdk/api";
import { HttpEndpoint, SdkRequest } from "@fern-fern/ir-sdk/api";
import { SdkGeneratorContext } from "../../SdkGeneratorContext";
import { ReferencedEndpointRequest } from "./ReferencedEndpointRequest";
import { WrappedEndpointRequest } from "./WrappedEndpointRequest";
import { RawClient } from "../RawClient";

export interface QueryParameterCodeBlock {
code: csharp.CodeBlock;
Expand Down Expand Up @@ -37,4 +36,6 @@ export abstract class EndpointRequest {
public abstract getHeaderParameterCodeBlock(): HeaderParameterCodeBlock | undefined;

public abstract getRequestBodyCodeBlock(): RequestBodyCodeBlock | undefined;

public abstract getRequestType(): RawClient.RequestBodyType | undefined;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { HttpEndpoint, SdkRequest, ServiceId } from "@fern-fern/ir-sdk/api";
import { SdkGeneratorContext } from "../../SdkGeneratorContext";
import { BytesOnlyEndpointRequest } from "./BytesOnlyEndpointRequest";
import { EndpointRequest } from "./EndpointRequest";
import { ReferencedEndpointRequest } from "./ReferencedEndpointRequest";
import { WrappedEndpointRequest } from "./WrappedEndpointRequest";
Expand Down Expand Up @@ -31,7 +32,7 @@ export function createEndpointRequest({
},
justRequestBody: (value) => {
if (value.type === "bytes") {
return undefined;
return new BytesOnlyEndpointRequest(context, sdkRequest, endpoint);
} else {
return new ReferencedEndpointRequest(context, sdkRequest, endpoint, value.requestBodyType);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { csharp } from "@fern-api/csharp-codegen";
import { HttpEndpoint, SdkRequest, TypeReference } from "@fern-fern/ir-sdk/api";
import { SdkGeneratorContext } from "../../SdkGeneratorContext";
import { RawClient } from "../RawClient";
import {
EndpointRequest,
HeaderParameterCodeBlock,
Expand Down Expand Up @@ -38,4 +39,8 @@ export class ReferencedEndpointRequest extends EndpointRequest {
requestBodyReference: this.getParameterName()
};
}

public getRequestType(): RawClient.RequestBodyType | undefined {
return "json";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
TypeReference
} from "@fern-fern/ir-sdk/api";
import { SdkGeneratorContext } from "../../SdkGeneratorContext";
import { RawClient } from "../RawClient";
import {
EndpointRequest,
HeaderParameterCodeBlock,
Expand Down Expand Up @@ -129,6 +130,20 @@ export class WrappedEndpointRequest extends EndpointRequest {
};
}

public getRequestType(): RawClient.RequestBodyType | undefined {
if (this.endpoint.requestBody == null) {
return undefined;
}
switch (this.endpoint.requestBody.type) {
case "bytes":
return "bytes";
case "reference":
case "inlinedRequestBody":
return "json";
}
return undefined;
}

private stringify({ reference, name }: { reference: TypeReference; name: Name }): csharp.CodeBlock {
if (this.isString(reference)) {
return csharp.codeblock(`${this.getParameterName()}.${name.pascalCase.safeName}`);
Expand Down Expand Up @@ -160,7 +175,11 @@ export class WrappedEndpointRequest extends EndpointRequest {
return undefined;
},
fileUpload: () => undefined,
bytes: () => undefined,
bytes: () => {
return {
requestBodyReference: `${this.getParameterName()}.${this.wrapper.bodyKey.pascalCase.safeName}`
};
},
_other: () => undefined
});
}
Expand Down
Loading
Loading