# README

// ReadMe API Client for Go is for performing API operations with ReadMe.com. // // Refer to https://docs.readme.com/main/reference/intro/getting-started for more information about // the ReadMe API. package readme

import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io" "net/http" "reflect" "regexp" "strconv" "strings" "time" )

// Markdown documentation generation. //go:generate go run github.com/princjef/gomarkdoc/cmd/gomarkdoc --output ../docs/README.md ./...

const ( // PaginationHeader is the name of the HTTP response header with pagination links. PaginationHeader = "link"

// ReadmeAPIURL is the default base URL for the ReadMe API.
ReadmeAPIURL = "https://dash.readme.com/api/v1"

// TotalCountHeader is the name of the HTTP response header with the total count in results.
TotalCountHeader = "x-total-count"

// UserAgent is the name of the HTTP UserAgent when making requests.
UserAgent = "readme-api-go-client"

)

// IDValidCharacters is a compiled RegEx pattern that matches valid characters in an object ID or // API Registry UUID. var IDValidCharacters = regexp.MustCompile("^[0-9a-zA-Z]+$")

// Client sets up the API HTTP client with authentication and exposes the API interfaces. type Client struct { // APIURL is the base URL for the ReadMe API. APIURL string // HTTPClient is the initialized HTTP client. HTTPClient *http.Client // Token is the API token for authenticating with ReadMe. Token string

// APIRegistry implements the ReadMe API Registry API for managing API definitions.
APIRegistry APIRegistryService
// APISpecification implements the ReadMe API Specification API for managing API specifications.
APISpecification APISpecificationService
// Apply implements the ReadMe API Apply API for retrieving and applying for positions at ReadMe.
Apply ApplyService
// Category implements the ReadMe Category API for managing categories.
Category CategoryService
// Changelog implements the ReadMe Changelog API for managing changelogs.
Changelog ChangelogService
// CustomPage implements the ReadMe CustomPage API for managing custom pages.
CustomPage CustomPageService
// Doc implements the ReadMe Docs API for managing docs.
Doc DocService
// Image implements the ReadMe Image API for uploading images.
Image ImageService
// OutboundIP implements the ReadMe OutboundIP API for retrieving outbound IP addresses.
OutboundIP OutboundIPService
// Project implements the ReadMe Project API for retrieving metadata about the project.
Project ProjectService
// Version implements the ReadMe Version API for managing versions.
Version VersionService

}

// RequestHeader represents an HTTP header set on requests. type RequestHeader map[string]string

// APIRequest represents a request to the ReadMe.com API. type APIRequest struct { // Endpoint is the API endpoint (after the base URL) for the request. Endpoint string

// Headers lists HTTP headers to send in the request, in addition to the implicit headers.
Headers []RequestHeader

// Slice of HTTP status codes that are considered 'ok'.
// Any other status code in the response results in an error.
OkStatusCode []int

// Method is the HTTP method to use for the request.
Method string

// An optional payload, in bytes, for the request.
Payload []byte

// Optional options for a request, including headers, version and pagination options.
RequestOptions

// Interface of a struct to map the response body to.
Response interface{}

// UseAuth toggles whether the request should use authentication or not.
UseAuth bool

// URL is a full URL string to use for the request as an alternative to Endpoint.
URL string

}

// APIResponse represents the response from a request to the ReadMe API. type APIResponse struct { // APIErrorResponse is a structured error from the ReadMe API when a request results in error. APIErrorResponse APIErrorResponse // Body is the response body in bytes. Body []byte // HTTPResponse is the stdlib http.Response type. HTTPResponse *http.Response // Request is the APIRequest struct used to create the request. Request *APIRequest }

// APIErrorResponse represents the response ReadMe provides in the body of requests that failed. type APIErrorResponse struct { // Docs is a ReadMe Metrics log URL where more information about the request can be retrieved. // If metrics URLs are unavailable for the request, this URL will be a URL to the ReadMe API Reference. Docs string json:"docs" // Error is an error code unique to the error received. Error string json:"error" // Help is information on where additional assistance from the ReadMe support team can be obtained. Help string json:"help" // Message is the reason why the error occurred. Message string json:"message" // Poem is a short poem about the error. Poem []string json:"poem" // Suggestion is a helpful suggestion for how to alleviate the error. Suggestion string json:"suggestion" }

// RequestOptions is used for specifying options for requests, such as pagination options. type RequestOptions struct { // Headers is a list of additional headers to add to the request. Headers []RequestHeader // PerPage is the number of items to return in each request when using pagination. // The maximum and default is 100. PerPage int // Page is the page number to request when using pagination. Page int // ProductionDoc is used by readme.Docs.Get() to indicate whether the requested document is a // 'production' doc. ProductionDoc bool // Version number of a ReadMe project, for example, v3.0. By default the main project version is used. Version string }

// NewClient initializes the API client configuration and returns the HTTP client with an auth token and URL set. // // Optionally provide a custom API URL as a second parameter. func NewClient(token string, apiURL ...string) (*Client, error) { client := &Client{ HTTPClient: &http.Client{Timeout: 10 * time.Second}, } client.APIURL = ReadmeAPIURL client.Token = token

if apiURL != nil {
	if len(apiURL) > 1 {
		return nil, fmt.Errorf("unable to configure ReadMe API client: "+
			"too many values specified for API URL (got: %v; expects 1)", len(apiURL))
	}
	client.APIURL = apiURL[0]
}

client.APIRegistry = &APIRegistryClient{client: client}
client.APISpecification = &APISpecificationClient{client: client}
client.Apply = &ApplyClient{client: client}
client.Category = &CategoryClient{client: client}
client.Changelog = &ChangelogClient{client: client}
client.CustomPage = &CustomPageClient{client: client}
client.Doc = &DocClient{client: client}
client.Image = &ImageClient{client: client}
client.OutboundIP = &OutboundIPClient{client: client}
client.Project = &ProjectClient{client: client}
client.Version = &VersionClient{client: client}

return client, nil

}

// APIRequest performs a request to the ReadMe API and handles parsing the response and API errors. // // This function is called directly by the receiver functions used to implement each endpoint. func (c *Client) APIRequest(request *APIRequest) (*APIResponse, error) { // Perform the request body, httpResponse, err := c.doRequest(request) if err != nil { return nil, err }

apiResponse := &APIResponse{
	Body:         body,
	HTTPResponse: &httpResponse,
	Request:      request,
}

// Verify the HTTP response from the API.
apiErrorResponse, err := checkResponseStatus(body, httpResponse.StatusCode, request)
if err != nil {
	apiResponse.APIErrorResponse = apiErrorResponse

	return apiResponse, err
}

// Parse the response into the specified interface.
if request.Response != nil {
	err = json.Unmarshal(body, &request.Response)
	if err != nil {
		return apiResponse, fmt.Errorf("unable to parse API response: %w", err)
	}
}

err = httpResponse.Body.Close()
if err != nil {
	return apiResponse, fmt.Errorf("problem closing HTTP response body")
}

return apiResponse, nil

}

// doRequest performs an API request and returns the response or error. func (c *Client) doRequest(request *APIRequest) ([]byte, http.Response, error) { req, err := c.prepareRequest(request) if err != nil { return nil, http.Response{}, err }

// Perform the request.
res, err := c.HTTPClient.Do(req)
if err != nil {
	return nil, http.Response{}, fmt.Errorf("unable to make request: %w", err)
}

if res.Body == nil {
	return nil, *res, fmt.Errorf("response body is nil in %s request to %s", req.Method, req.URL)
}

body, err := io.ReadAll(res.Body)
if err != nil {
	return nil, *res, fmt.Errorf("unable to read response: %w", err)
}

err = res.Body.Close()
if err != nil {
	return nil, *res, fmt.Errorf("problem closing HTTP response body")
}

return body, *res, nil

}

// checkResponseStatus compares an HTTP response status code against a slice of 'OK' status codes. // // If the response code matches a provided code listed in okCodes, no error is returned. // If the response code doesn't match, an error and APIErrorResponse is returned. func checkResponseStatus(body []byte, responseCode int, req *APIRequest) (APIErrorResponse, error) { var apiErrorResponse APIErrorResponse for _, okCode := range req.OkStatusCode { if responseCode == okCode { return apiErrorResponse, nil } }

err := json.Unmarshal(body, &apiErrorResponse)
if err != nil {
	return apiErrorResponse, fmt.Errorf("unable to decode API error response: %w", err)
}

return apiErrorResponse,
	fmt.Errorf("ReadMe API Error: %v on %s %s: %s",
		responseCode, req.Method, req.Endpoint, body)

}

// prepareRequest prepares an http.Request for the ReadMe API. // // This sets common headers and prepares an optional payload for the request. func (c *Client) prepareRequest(request *APIRequest) (*http.Request, error) { // Prepare the request. if request.URL == "" { request.URL = c.APIURL + request.Endpoint } req, reqErr := http.NewRequest(request.Method, request.URL, nil)

if request.Payload != nil {
	data := bytes.NewBuffer(request.Payload)
	req, reqErr = http.NewRequest(request.Method, request.URL, data)
}

if reqErr != nil {
	return nil, fmt.Errorf("unable to prepare request: %w", reqErr)
}

for _, r := range request.Headers {
	for header, value := range r {
		req.Header.Set(header, value)
	}
}

if request.UseAuth {
	encodedToken := base64.StdEncoding.EncodeToString([]byte(c.Token))
	authHeader := "Basic " + encodedToken
	req.Header.Set("authorization", authHeader)
}

if request.RequestOptions.Version != "" {
	req.Header.Set("x-readme-version", request.RequestOptions.Version)
}

req.Header.Set("accept", "application/json")
req.Header.Set("User-Agent", UserAgent)

return req, nil

}

// paginatedRequest makes a request to the ReadMe API with pagination query parameters set. // // An abbreviated *APIRequest struct should be passed, leaving the Headers and Version fields unset. // These are derived from the RequestOptions field. // // This function is intended to be called within a loop and returns the APIResponse struct and a // boolean indicating if there is a next page indicated in the pagination header. func (c *Client) paginatedRequest(apiRequest *APIRequest, page int) (*APIResponse, bool, error) { // Set default values perPage := 100

// Check for custom values in RequestOptions
if apiRequest.RequestOptions.PerPage != 0 {
	perPage = apiRequest.RequestOptions.PerPage
}
if apiRequest.RequestOptions.Headers != nil {
	apiRequest.Headers = apiRequest.RequestOptions.Headers
}

// Add pagination parameters to endpoint
baseEndpoint := apiRequest.Endpoint
apiRequest.Endpoint = fmt.Sprintf("%s?perPage=%d&page=%d", baseEndpoint, perPage, page)

if apiRequest.URL == "" {
	apiRequest.URL = c.APIURL + apiRequest.Endpoint
}

// Make API request
apiResponse, err := c.APIRequest(apiRequest)
if err != nil {
	return apiResponse, false, fmt.Errorf("unable to make request: %w", err)
}

// Check for next page
hasNextPage, err := HasNextPage(apiResponse.HTTPResponse.Header.Get(PaginationHeader))
if err != nil {
	return apiResponse, false, fmt.Errorf(
		"unable to check pagination link header '%s': %w; ",
		PaginationHeader,
		err,
	)
}
if !hasNextPage {
	return apiResponse, false, nil
}

// Get total count of items
totalCountHeader := apiResponse.HTTPResponse.Header.Get(TotalCountHeader)
totalCount, err := strconv.Atoi(totalCountHeader)
if err != nil {
	return apiResponse, false, fmt.Errorf(
		"unable to parse '%s' header: %w; Response: %v",
		TotalCountHeader,
		err,
		apiResponse,
	)
}

// Check if current page is last page
if page >= (totalCount / perPage) {
	return apiResponse, false, nil
}

return apiResponse, true, nil

}

func (c *Client) fetchAllPages( endpoint string, options *RequestOptions, result interface{}, ) (*APIResponse, error) { hasNextPage := false page := 1 var apiResponse *APIResponse var err error

// Convert the result to a reflect.Value to manipulate the underlying slice
resultValue := reflect.ValueOf(result)
if resultValue.Kind() != reflect.Ptr || resultValue.Elem().Kind() != reflect.Slice {
	return nil, fmt.Errorf("result argument must be a pointer to a slice")
}

if options != nil && options.Page != 0 {
	page = options.Page
}

for {
	pageResults := reflect.New(resultValue.Elem().Type()).Interface()

	apiRequest := &APIRequest{
		Method:       "GET",
		Endpoint:     endpoint,
		UseAuth:      true,
		OkStatusCode: []int{200},
		Response:     pageResults,
	}
	if options != nil {
		apiRequest.RequestOptions = *options
	}

	apiResponse, hasNextPage, err = c.paginatedRequest(apiRequest, page)
	if err != nil {
		return apiResponse, fmt.Errorf("unable to retrieve data: %w", err)
	}

	// Append the current page's results to the original result slice
	resultValue.Elem().Set(reflect.AppendSlice(resultValue.Elem(), reflect.ValueOf(pageResults).Elem()))

	if !hasNextPage {
		break
	}
	page++
}

return apiResponse, nil

}

// parseRequestOptions is a helper function to parse the RequestOptions slice // and return the first element as a *RequestOptions struct. func parseRequestOptions(options []RequestOptions) *RequestOptions { if len(options) > 0 { return &options[0] } return nil }

// HasNextPage checks if a "next" link is provided in the "links" response header for pagination, // indicating the request has a next page. // // This does a rudimentary parsing of the header value, splitting on the comma-separated links and // parsing the value of "rel". // // A link header looks like: // </api-specification?page=2>; rel="next", <>; rel="prev", <>; rel="last" func HasNextPage(links string) (bool, error) { // Split links by comma parts := strings.Split(links, ",")

// Return error if invalid format
if len(parts) < 3 {
	return false, fmt.Errorf("unable to parse link header - invalid format: "+
		"'%s'; expected "+`'<>; rel="next", <>; rel="prev", <>; rel="last"'`, links)
}

// Check for "rel=next" in parts
for _, part := range parts {
	rel := strings.Split(part, ";")
	if len(rel) != 2 {
		return false, fmt.Errorf("unable to parse link header - invalid format: "+
			"'%s'; expected "+`'<>; rel="next", <>; rel="prev", <>; rel="last"'`, links)
	}
	if rel[1] == " rel=\"next\"" && rel[0] != "<>" {
		return true, nil
	}
}

// Return false if "rel=next" is not found
return false, nil

}

// ValidateID is a helper script for parseID() and parseUUID() that checks a string to determine if // it appears to be a valid ReadMe API object ID or Registry UUID. func ValidateID(id, prefix string, min_len, max_len int) (bool, string) { if !strings.HasPrefix(id, prefix+":") { return false, "" }

parts := strings.Split(id, ":")

if len(parts[1]) < min_len || len(parts[1]) > max_len {
	return false, ""
}

return IDValidCharacters.MatchString(parts[1]), parts[1]

}

// ParseUUID checks a string to determine if it appears to be a valid ReadMe API Registry UUID. // // The provided parameter should be a ReadMe API Registry UUID prefixed with "uuid:". // // NOTE: The min and max lengths aren't certain or documented in the API. The UUID length varies. func ParseUUID(uuid string) (bool, string) { return ValidateID(uuid, "uuid", 10, 24) }

// ParseID checks a string to determine if it appears to be a valid ReadMe API object ID. // // The provided parameter should be a ReadMe API object ID prefixed with "id:". // // NOTE: The min and max lengths aren't certain or documented in the API. func ParseID(id string) (bool, string) { return ValidateID(id, "id", 20, 24) }

# Functions

HasNextPage checks if a "next" link is provided in the "links" response header for pagination, indicating the request has a next page.
NewClient initializes the API client configuration and returns the HTTP client with an auth token and URL set.
ParseID checks a string to determine if it appears to be a valid ReadMe API object ID.
ParseUUID checks a string to determine if it appears to be a valid ReadMe API Registry UUID.
ValidateID is a helper script for parseID() and parseUUID() that checks a string to determine if it appears to be a valid ReadMe API object ID or Registry UUID.

# Constants

APIRegistryEndpoint is the ReadMe API URL endpoint for the API registry.
APISpecificationEndpoint is the ReadMe API Endpoint for API Specifications.
APISpecificationFormField is the name of the form field for uploading API Specifications.
ApplyEndpoint is the ReadMe API URL endpoint for listing open positions at ReadMe and applying for a them.
CategoryEndpoint is the ReadMe API endpoint for categories.
ChangelogEndpoint is ReadMe API Endpoint for changelogs.
CustomPageEndpoint is the ReadMe API Endpoint for custom pages.
DocEndpoint is the ReadMe API endpoint for docs.
ImageAPIURL is the base URL for the images endpoint of the ReadMe API.
OutboundIPEndpoint is the ReadMe API URL endpoint for retrieving ReadMe's outbound IP addresses.
PaginationHeader is the name of the HTTP response header with pagination links.
ProjectEndpoint is the ReadMe API URL endpoint for Project metadata.
ReadmeAPIURL is the default base URL for the ReadMe API.
TotalCountHeader is the name of the HTTP response header with the total count in results.
UserAgent is the name of the HTTP UserAgent when making requests.
VersionEndpoint is the ReadMe API URL endpoint for Version metadata.

# Variables

IDValidCharacters is a compiled RegEx pattern that matches valid characters in an object ID or API Registry UUID.

# Structs

APIErrorResponse represents the response ReadMe provides in the body of requests that failed.
APIRegistryClient handles communication with the Registry related methods of the ReadMe.com API.
APIRegistrySaved represents the API response when an API Registry is created.
APIRequest represents a request to the ReadMe.com API.
APIResponse represents the response from a request to the ReadMe API.
APISpecification represents an API specification on ReadMe.com.
APISpecificationClient handles communication with the API specification related methods of the ReadMe.com API.
APISpecificationSaved represents a successful response to creating an API specification on ReadMe.com.
Application represents the parameters used for submitting an application to ReadMe.
ApplyClient handles communication with the Apply related methods of the ReadMe.com API.
ApplyResponse represents the API response when an application is submitted.
Category represents a single category in ReadMe.
CategoryClient handles communication with the categories related methods of the ReadMe.com API.
CategoryDocs represents a document within a category.
CategoryParams represents the parameters to create or update a category in ReadMe.
CategorySaved represents the ReadMe API response when a category is created or updated.
CategorySummary represents basic information about a category.
No description provided by the author
No description provided by the author
No description provided by the author
Changelog represents a Changelog object on ReadMe.
ChangelogClient handles communication with the docs related methods of the ReadMe.com API.
ChangelogParams represents the parameters for creating and updating a Changelog on ReadMe.
Client sets up the API HTTP client with authentication and exposes the API interfaces.
CustomPage represents a custom page in ReadMe.
CustomPageClient handles communication with the custom page related methods of the ReadMe API.
CustomPageParams represents the parameters to create or update a custom page in ReadMe.
Doc represents a doc in ReadMe.
DocAlgolia represents the corresponding 'algolia' key in the API response for a doc.
DocAPI represents a doc's API data in ReadMe.
DocAPIExamples represents the "api:examples" object returned for a doc.
DocAPIExamplesCodes represents the "api:examples:codes" object returned for a doc.
DocAPIParams represents the "api:params" object returned for a doc.
DocAPIResults represents the "api:results" object returned for a doc.
DocAPIResultsCodes represents the "api:results:codes" object returned for a doc.
DocClient handles communication with the docs related methods of the ReadMe.com API.
DocErrorObject represents the 'error' key in a doc response.
DocMetadata represents a doc's metadata in ReadMe.
DocNext represents a doc's "next" doc and pages in ReadMe.
DocNextPages represents the "next:pages" object returned for a doc.
DocParams represents the parameters for creating a doc on ReadMe.
DocSearchResult represents a single item in the list of search results.
DocSearchResultHighlight represents the HighlightResult key in a search result item.
DocSearchResultHighlightValue represents the HighlightResult child keys in a search result item.
DocSearchResults represents the response from searching.
DocSearchResultSnippet represents the SnippetResult key in a search result item.
DocSearchResultSnippetValue represents the SnippetResult child keys in a search result item.
DocSwagger represents the optional "swagger" object returned for a doc.
DocUser represents the corresponding key in the API response for a user object on ReadMe.
Image represents an image uploaded to ReadMe.
ImageClient handles uploading images to ReadMe.com.
OpenRole represents an open role at ReadMe.
OutboundIP represents the response from the ReadMe API when returning outbound IP addresses.
OutboundIPClient handles communication with the OutboundIP related methods of the ReadMe.com API.
Project represents the response from the ReadMe API when returning project metadata.
ProjectClient handles communication with the Project related methods of the ReadMe.com API.
RequestOptions is used for specifying options for requests, such as pagination options.
Version represents the details of a specific version.
VersionClient handles communication with the Project related methods of the ReadMe.com API.
VersionParams represents the request parameters used when creating or updating a version.
VersionSummary represents the response from the ReadMe API when retrieving a list of versions with GetAll().

# Interfaces

APIRegistryService is an interface for interacting with the API Registry endpoints of the ReadMe.com API.
APISpecificationService is an interface for using the API Specification endpoints of the ReadMe.com API.
ApplyService is an interface for interacting with the Apply endpoints of the ReadMe.com API.
CategoryService is an interface for using the category endpoints of the ReadMe.com API.
ChangelogService is an interface for using the changelog endpoints of the ReadMe.com API.
CustomPageService is an interface for using the custom page endpoints of the ReadMe API.
DocService is an interface for using the docs endpoints of the ReadMe.com API.
ImagesService is an interface for using the docs endpoints of the ReadMe.com API.
OutboundIPService is an interface for using the outbound IPs endpoint of the ReadMe.com API.
ProjectService is an interface for using the projects endpoints of the ReadMe.com API.
VersionService is an interface for using the version endpoints of the ReadMe.com API.

# Type aliases

RequestHeader represents an HTTP header set on requests.