Skip to content

[http-client-js] Improve documentation #6940

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 3 commits into from
Closed
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
7 changes: 7 additions & 0 deletions .chronus/changes/joheredi-js-docs-2025-3-10-1-15-12.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/http-client-js"
---

Add code documentation
Original file line number Diff line number Diff line change
@@ -1,20 +1,53 @@
/**
* This file defines component that generates a declaration of a client context interface.
* The generated interface extends from a HTTP runtime client interface defined in httpRuntimeTemplateLib.
*
* The main exports are:
* - ClientContextDeclaration: A JSX component that outputs a TypeScript interface declaration.
* - getClientcontextDeclarationRef: A helper function to generate a unique reference key for the client context interface declaration.
*/

import * as ay from "@alloy-js/core";
import * as ts from "@alloy-js/typescript";
import * as cl from "@typespec/http-client";
import { httpRuntimeTemplateLib } from "../external-packages/ts-http-runtime.js";

/**
* Define the properties accepted by the ClientContextDeclaration component
*/
export interface ClientContextDeclarationProps {
client: cl.Client;
}

/**
* Generates a unique reference key for the client context declaration.
*
* @param client - The client object for which the reference key is generated.
* @returns A unique reference key string associated with the client's context interface declaration.
*/
export function getClientcontextDeclarationRef(client: cl.Client) {
return ay.refkey(client, "declaration");
}

/**
* A component that generates a TypeScript interface declaration for a client context.
*
* The interface is named using the client name appended with "Context" and is exported.
* It also inherits from the HTTP runtime client interface provided by httpRuntimeTemplateLib.
*
* @returns A component that generates the client context interface.
*/
export function ClientContextDeclaration(props: ClientContextDeclarationProps) {
// Generate a unique reference for the client's declaration context
const ref = getClientcontextDeclarationRef(props.client);

// Get the name policy utility to generate a proper TypeScript name for the context interface
const namePolicy = ts.useTSNamePolicy();

// Generate the interface name by appending "Context" to the client's name and ensuring it's a valid class name
const name = namePolicy.getName(`${props.client.name}Context`, "class");

// Return a TypeScript interface declaration element, setting it to export and extend the HTTP runtime client interface
return (
<ts.InterfaceDeclaration
export
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
/**
* This file defines a ClientContext component. It composes several client-related
* declarations (context, options, factory) into one coherent source file.
*/

import * as ay from "@alloy-js/core";
import * as ts from "@alloy-js/typescript";
import * as cl from "@typespec/http-client";
import { ClientContextDeclaration } from "./client-context-declaration.jsx";
import { ClientContextFactoryDeclaration } from "./client-context-factory.jsx";
import { ClientContextOptionsDeclaration } from "./client-context-options.jsx";

export interface ClientContextProps {
/**
* The HTTP client object that provides configuration details
*/
client: cl.Client;
/**
* Optional nested child elements (not used in this component).
*/
children?: ay.Children;
}

/**
* A component that creates a TypeScript source file containing
* various client context declarations. It composes declarations for the
* client context itself, its options, and a factory to generate instances.
*
* @param props - The properties for configuring the client context.
* @returns A component encapsulating the client declarations.
*/
export function ClientContext(props: ClientContextProps) {
const namePolicy = ts.useTSNamePolicy();
const fileName = namePolicy.getName(props.client.name + "Context", "variable");

return (
<ts.SourceFile path={`${fileName}.ts`}>
<ClientContextDeclaration client={props.client} />
Expand Down
24 changes: 24 additions & 0 deletions packages/http-client-js/src/components/encoding-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,41 @@
/**
* This file defines the EncodingProvider component which wraps its children with an encoding context.
* This context includes default values for encoding configurations such as "bytes" and "datetime".
* The component is useful when you need consistent encoding options across different parts of the emitter
* or when you want to override the default settings with custom values.
*/

import { Children } from "@alloy-js/core";
import { EncodingContext } from "../context/encoding/encoding-context.jsx";
import { EncodingDefaults } from "../context/encoding/types.js";

/**
* Interface for the properties accepted by the EncodingProvider component.
*
* @property {EncodingDefaults} [defaults] - Optional custom default encoding values that override built-in defaults.
* @property {Children} [children] - Nested elements/components that will have access to the encoding context.
*/
export interface EncodingProviderProps {
defaults?: EncodingDefaults;
children?: Children;
}

/**
* The EncodingProvider component wraps its children with a context that supplies encoding defaults.
* It merges user-supplied defaults with built-in default values. If no user defaults are provided, the
* encoding context will use "none" for 'bytes' and "rfc3339" for 'datetime'.
*
* @param {EncodingProviderProps} props - The props for the provider, including potential default values and children.
* @returns A JSX element that provides the encoding context to its children.
*/
export function EncodingProvider(props: EncodingProviderProps) {
// Create a merged defaults object: built-in values are used unless overridden by props.defaults.
const defaults: EncodingDefaults = {
bytes: "none",
datetime: "rfc3339",
...props.defaults,
};

// Wrap children with the EncodingContext.Provider to ensure they receive the defined defaults.
return <EncodingContext.Provider value={defaults}>{props.children}</EncodingContext.Provider>;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { createPackage } from "@alloy-js/typescript";

/**
* This defines the external @typespec/ts-http-runtime, registering its symbols for
* their use throughout the emitter.
*/
export const httpRuntimeTemplateLib = createPackage({
name: "@typespec/ts-http-runtime",
version: "0.2.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { createPackage } from "@alloy-js/typescript";

/**
* This defines the external uri-template, registering its symbols for
* their use throughout the emitter.
*/
export const uriTemplateLib = createPackage({
name: "uri-template",
version: "^2.0.0",
Expand Down
33 changes: 32 additions & 1 deletion packages/http-client-js/src/components/http-response.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
/**
* This file contains helper components to generate HTTP responses handling.
* The code processes responses by status code and content type, and throws custom REST errors when the response
* does not match any in the spec.
* It uses the Alloy JS framework and Typespec HTTP utilities to flatten and process HTTP responses.
*/

import { Children, code, For, List, Refkey } from "@alloy-js/core";
import { isVoidType } from "@typespec/compiler";
import { $ } from "@typespec/compiler/experimental/typekit";
import { HttpOperation } from "@typespec/http";
import { getCreateRestErrorRefkey } from "./static-helpers/rest-error.jsx";
import { ContentTypeEncodingProvider } from "./transforms/content-type-encoding-provider.jsx";
import { JsonTransform } from "./transforms/json/json-transform.jsx";

export interface HttpResponseProps {
httpOperation: HttpOperation;
responseRefkey: Refkey;
children?: Children;
}

/**
* HttpResponse component that renders a list of HTTP response handling code.
* It includes conditional handling for the success responses and a fallback error throwing.
*
* @param props - Properties including the HTTP operation and a reference key for the response.
*/
export function HttpResponse(props: HttpResponseProps) {
return (
<List hardline>
Expand All @@ -25,20 +39,34 @@ export interface HttpResponsesProps {
children?: Children;
}

/**
* HttpResponses component that iterates over HTTP responses extracted from the httpOperation.
* It generates conditional checks for each non-error response based on status code and content type.
*
* @param props - Properties containing the HttpOperation to be processed.
*/
export function HttpResponses(props: HttpResponsesProps) {
// Handle response by status code and content type
// Obtain and flatten the HTTP responses from the operation, filtering by non-error responses.
Copy link
Member

Choose a reason for hiding this comment

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

Feel like this would be more useful if we could add the typekit description to it?
"Get the responses for the given operation. This function will return an array of responses grouped by status code and content type."

const responses = $.httpOperation.flattenResponses(props.httpOperation);

return (
<For each={responses.filter((r) => !$.httpResponse.isErrorResponse(r))}>
{({ statusCode, contentType, responseContent, type }) => {
// Extract the body from the response content
Copy link
Member

Choose a reason for hiding this comment

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

Is this also auto generated by AI? how did it know which lines to add and which not?

const body = responseContent.body;

// Default expression to return if no valid body processing is applicable.
let expression: Children = code`return;`;

// Build a content type check condition:
// If a body exists, check if the response headers include the expected content type.
// Otherwise, ensure that the response has no body.
const contentTypeCheck = body
? ` && response.headers["content-type"]?.includes("${contentType}")`
: " && !response.body";

// If there is a response body and (it's single (aka not multipart nor file) or the type is not void),
// apply JSON transformation wrapped with the content-type encoding provider.
if (body && (body.bodyKind === "single" || (type && !isVoidType(type)))) {
expression = (
<ContentTypeEncodingProvider contentType={contentType}>
Expand All @@ -49,6 +77,7 @@ export function HttpResponses(props: HttpResponsesProps) {
);
}

// If the status code represents a single status value, generate an if-statement to match it.
if ($.httpResponse.statusCode.isSingle(statusCode)) {
return code`
if (+response.status === ${statusCode}${contentTypeCheck}) {
Expand All @@ -57,6 +86,7 @@ export function HttpResponses(props: HttpResponsesProps) {
`;
}

// If the status code represents a range, generate an if-statement to match the range.
if ($.httpResponse.statusCode.isRange(statusCode)) {
return code`
if (+response.status >= ${statusCode.start} && +response.status <= ${statusCode.end} ${contentTypeCheck}) {
Expand All @@ -65,6 +95,7 @@ export function HttpResponses(props: HttpResponsesProps) {
`;
}

// Return null if the statusCode type is not handled explicitly.
return null;
}}
</For>
Expand Down
34 changes: 34 additions & 0 deletions packages/http-client-js/src/components/models.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,62 @@
/**
* This file defines a component that generates TypeScript type declarations
* for model types using the client library. It iterates over each data type provided
* by the client library and conditionally generates corresponding TypeScript code.
* The generated code handles both HTTP file models and regular type declarations.
*/

import { For, refkey } from "@alloy-js/core";
import * as ts from "@alloy-js/typescript";
import { $ } from "@typespec/compiler/experimental/typekit";
import * as ef from "@typespec/emitter-framework/typescript";
import { useClientLibrary } from "@typespec/http-client";
import { getFileTypeReference } from "./static-helpers/multipart-helpers.jsx";

/**
* This interface defines the expected props for the Models component.
* The 'path' property is optional and represents the file path for the generated TypeScript file.
*/
export interface ModelsProps {
path?: string;
}

/**
* Models component
*
* This component generates a TypeScript source file containing type declarations
* based on the data types obtained from the client library. It composes the source
* file and conditionally renders type declarations for HTTPFile models and other
* model types.
*
* @param props - Props passed to the component; includes an optional file 'path'.
* @returns A component representing a TypeScript source file with the generated type declarations.
*/
export function Models(props: ModelsProps) {
// Client library hook provides access to data types
const clientLibrary = useClientLibrary();
// Extract the array of data types from the client library
// These have been collected when walking the client hierarchy
const dataTypes = clientLibrary.dataTypes;

return (
// Create a TypeScript source file with the specified or default path "models.ts"
// Iterate over each data type from the client library.
// For each type, determine the appropriate TypeScript type declaration to generate.
<ts.SourceFile path={props.path ?? "models.ts"}>
<For each={dataTypes} joiner={"\n"} hardline>
{(type) => {
// If the data type is a model and represents an HTTP file,
// generate a special 'File' type declaration using the file type reference.
if ($.model.is(type) && $.model.isHttpFile(type)) {
return (
<ts.TypeDeclaration name="File" export kind="type" refkey={refkey(type)}>
{getFileTypeReference()}
</ts.TypeDeclaration>
);
}
// If the type is an array or a record, skip rendering a declaration (return null)
// we just need to declare the type wrapped in the array or record.
// Otherwise, generate a standard type declaration using the emitter framework.
return $.array.is(type) || $.record.is(type) ? null : (
<ef.TypeDeclaration export type={type} />
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
/**
* Component responsible for managing the operation handler pipeline.
*
* This component iterates through a list of handlers to determine
* which handler can process the given HTTP operation. It uses the first handler
* that returns a truthy value for the canHandle method, and then renders the
* result of its handle method. If no handler is able to process the operation,
* it falls back to rendering a default client operation component.
*/

import * as ay from "@alloy-js/core";
import { HttpOperation } from "@typespec/http";
import { ClientOperation as DefaultOperationComponent } from "../client-operation.jsx";
import { OperationHandlerPipeline } from "./types.jsx";

/**
* Props for OperationPipeline component.
*
* @param httpOperation - The HTTP operation to be processed.
* @param pipeline - The pipeline containing operation handlers.
* @param internal - Optional flag indicating if the operation is internal.
*/
export interface OperationPipelineProps {
httpOperation: HttpOperation;
pipeline: OperationHandlerPipeline;
Expand All @@ -13,16 +30,26 @@ export interface OperationPipelineProps {
* Component that manages the operation handler pipeline.
* It will try each handler in order until it finds one that can handle the operation.
* If no handler is found, it will use the default ClientOperation component.
*
* @param props - OperationPipelineProps object containing:
* - httpOperation: the HTTP operation instance.
* - pipeline: the pipeline of registered operation handlers.
* - internal (optional): flag to indicate if the operation is internal.
* @returns Component that represents the processed operation.
*/
export function OperationPipeline({ httpOperation, pipeline, internal }: OperationPipelineProps) {
// Iterate through each available handler in the pipeline
for (const handler of pipeline.handlers) {
// Check if the current handler can process the provided HTTP operation
if (handler.canHandle(httpOperation)) {
// If yes, delegate the handling to the specific handler and return the result
return handler.handle(httpOperation);
}
}

// Default to standard client operation if no handler matched
// If no handler matches, generate a unique reference key for the operation
const defaultOperationRefkey = ay.refkey(httpOperation.operation);
// Default to standard client operation if no handler was able to process the operation
return (
<DefaultOperationComponent
httpOperation={httpOperation}
Expand Down
Loading
Loading