IPATool HTTP Client Response Formats: JSON, RAW, and XML Explained
The IPATool HTTP client supports three distinct response formats—JSON, RAW, and XML—allowing developers to decode server replies into typed Go structs, raw byte slices, or Apple Property List (plist) structures.
The majd/ipatool repository implements a generic HTTP client (Client[R]) that adapts its decoding strategy based on the ResponseFormat field specified in each request. This design enables seamless interaction with Apple's App Store APIs, which return a mix of JSON metadata and binary plist responses.
Supported Response Formats
The IPATool HTTP client provides three format identifiers defined in pkg/http/constants.go. Each format determines how the raw HTTP body is processed before being returned to the caller.
JSON Format (ResponseFormatJSON)
When using JSON format, the client unmarshals the response body into the generic type R using Go's standard json.Unmarshal. This is the default choice for REST API interactions that return structured data.
In pkg/http/client.go, the handleJSONResponse method reads the entire response body and attempts to decode it into the requested type. If the JSON is malformed or cannot map to the generic type R, the error propagates back to the caller.
RAW Format (ResponseFormatRaw)
The RAW format returns the unprocessed []byte slice directly from the HTTP response body without any parsing or unmarshaling. This format is strictly constrained: the generic type R must be []byte, or the client returns an error stating "raw response format requires a []byte result type".
According to the source in pkg/http/client.go (lines 49-58), this guard clause prevents type mismatches when callers attempt to use RAW mode with incompatible struct types. RAW mode is ideal for downloading binary files or when you need to process the response body manually.
XML Format (ResponseFormatXML)
The XML format handles Apple's XML Property List (plist) responses, which are common in App Store authentication and purchase flows. Rather than generic XML parsing, this format specifically expects plist-encoded data.
In pkg/http/client.go (lines 42-46), the handleXMLResponse method normalizes the XML payload, verifies it resembles a valid plist structure, and unmarshals it using plist.Unmarshal. If the payload is not a valid plist, the client wraps the error in an UnexpectedResponseError with descriptive context.
How the Client Selects Response Handlers
During request execution in pkg/http/client.go, the client evaluates the ResponseFormat field of the Request struct to determine which handler to invoke:
if req.ResponseFormat == ResponseFormatJSON {
return c.handleJSONResponse(res)
}
if req.ResponseFormat == ResponseFormatRaw {
return c.handleRawResponse(res)
}
if req.ResponseFormat == ResponseFormatXML {
return c.handleXMLResponse(res)
}
If the ResponseFormat value does not match any of the three supported constants, the client returns an error: "content type is not supported". This explicit routing ensures type-safe decoding based on the expected response structure.
Source Code Structure
Understanding the implementation requires examining three key files in the pkg/http package:
Constants Definition (pkg/http/constants.go)
This file declares the ResponseFormat type as a string alias and defines the three exported constants: ResponseFormatJSON, ResponseFormatRaw, and ResponseFormatXML (lines 6-8). These constants are used both by the client implementation and by consumers of the API when constructing requests.
Client Implementation (pkg/http/client.go)
The core logic resides here, implementing the generic Client[R] struct and its Send method. This file contains the three handler methods—handleJSONResponse, handleRawResponse, and handleXMLResponse—along with the type validation logic for the RAW format (lines 42-58).
Request Configuration (pkg/http/request.go)
The Request struct defined here includes the ResponseFormat field, allowing callers to specify their expected response type on a per-request basis. This design makes the client flexible enough to handle different endpoint types within the same session.
Practical Usage Examples
The following examples demonstrate how to configure the IPATool HTTP client for each response format.
JSON Response Handling
Use this approach when consuming REST endpoints that return JSON metadata:
package main
import (
"fmt"
"github.com/majd/ipatool/pkg/http"
)
func fetchJSON() {
client := http.NewClient[map[string]any](http.Args{})
req := http.Request{
Method: http.MethodGET,
URL: "https://example.com/api/json",
ResponseFormat: http.ResponseFormatJSON,
}
res, err := client.Send(req)
if err != nil {
panic(err)
}
fmt.Printf("JSON data: %+v\n", res.Data)
}
RAW Binary Response Handling
Use this when downloading binary data or when you need to inspect the raw HTTP body:
package main
import (
"fmt"
"github.com/majd/ipatool/pkg/http"
)
func fetchRaw() {
// Note: Generic type must be []byte for RAW format
client := http.NewClient[[]byte](http.Args{})
req := http.Request{
Method: http.MethodGET,
URL: "https://example.com/api/raw",
ResponseFormat: http.ResponseFormatRaw,
}
res, err := client.Send(req)
if err != nil {
panic(err)
}
fmt.Printf("Raw bytes length: %d\n", len(res.Data))
}
XML Property List Handling
Use this for Apple-specific endpoints returning plist-encoded data:
package main
import (
"fmt"
"github.com/majd/ipatool/pkg/http"
"github.com/majd/ipatool/pkg/http/payload"
)
func fetchXML() {
client := http.NewClient[map[string]any](http.Args{})
req := http.Request{
Method: http.MethodPOST,
URL: "https://example.com/api/xml",
Payload: payload.NewJSON(map[string]string{"foo": "bar"}),
ResponseFormat: http.ResponseFormatXML,
}
res, err := client.Send(req)
if err != nil {
panic(err)
}
fmt.Printf("XML-decoded data: %+v\n", res.Data)
}
Error Handling and Constraints
Each response format enforces specific constraints that trigger errors if violated:
- RAW format type safety: If the generic type
Ris not[]byte, the client immediately returns an error before making the HTTP request. - XML plist validation: If the response body cannot be parsed as a valid plist, the client returns an
UnexpectedResponseErrorwrapping the underlying plist parsing failure. - Unsupported formats: Supplying an unrecognized
ResponseFormatvalue results in a "content type is not supported" error.
These strict validations prevent runtime type assertion panics and ensure that decoding errors are caught early in the response lifecycle.
Summary
- The IPATool HTTP client supports JSON, RAW, and XML response formats, defined as constants in
pkg/http/constants.go. - JSON unmarshals into generic type
Rusing standard Go JSON parsing, suitable for REST APIs. - RAW returns unprocessed
[]byteslices but requires the client to be instantiated with[]byteas the generic type parameter. - XML specifically handles Apple plist responses using
plist.Unmarshaland validates the payload structure before decoding. - The client selects handlers via explicit conditional checks in
pkg/http/client.gobased on theRequest.ResponseFormatfield.
Frequently Asked Questions
What happens if I use RAW format with a non-byte generic type?
The client returns an immediate error stating "raw response format requires a []byte result type" without executing the HTTP request. This guard clause in pkg/http/client.go (lines 49-58) ensures type safety by validating the generic parameter R against []byte during the request preparation phase.
Can I extend the client to support additional response formats?
The current implementation in pkg/http/client.go uses a hardcoded conditional chain to select between JSON, RAW, and XML handlers. Adding new formats would require modifying the source code to define a new constant in constants.go, implement a corresponding handler method in client.go, and update the conditional logic in the Send method to route to the new handler.
Why does the XML format use plist-specific parsing instead of generic XML?
Apple's App Store and iTunes APIs frequently return XML Property Lists (plists) rather than standard XML documents. The handleXMLResponse method in pkg/http/client.go specifically uses plist.Unmarshal because the tool primarily interacts with Apple's proprietary endpoints. Generic XML parsing would fail to properly decode these plist-specific data types and structures.
Is there a performance difference between RAW and JSON formats?
RAW format avoids the CPU overhead of JSON unmarshaling and memory allocation for intermediate structures, making it faster for large payloads or binary data. However, for small JSON responses, the difference is negligible. Choose RAW when you need the raw bytes for file downloads or custom parsing, and JSON when you need structured data mapped to Go types.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →