← back to Cli Printing Press
fix(cli): preserve operation server host overrides (#834)
1c0d110bb6ef79822ed226d6e961682d27b03c0a · 2026-05-09 12:31:43 -0700 · Trevin Chow
Files touched
M docs/SPEC-EXTENSIONS.mdM internal/generator/generator.goM internal/generator/generator_test.goM internal/generator/templates/client.go.tmplM internal/generator/templates/command_endpoint.go.tmplM internal/generator/templates/command_promoted.go.tmplM internal/openapi/parser.goM internal/openapi/parser_test.goM internal/spec/spec.goM skills/printing-press/references/spec-format.md
Diff
commit 1c0d110bb6ef79822ed226d6e961682d27b03c0a
Author: Trevin Chow <trevin@trevinchow.com>
Date: Sat May 9 12:31:43 2026 -0700
fix(cli): preserve operation server host overrides (#834)
---
docs/SPEC-EXTENSIONS.md | 4 +-
internal/generator/generator.go | 90 ++++++++++++----------
internal/generator/generator_test.go | 37 +++++++++
internal/generator/templates/client.go.tmpl | 8 +-
.../generator/templates/command_endpoint.go.tmpl | 2 +-
.../generator/templates/command_promoted.go.tmpl | 2 +-
internal/openapi/parser.go | 19 +++++
internal/openapi/parser_test.go | 41 ++++++++++
internal/spec/spec.go | 25 ++++--
skills/printing-press/references/spec-format.md | 6 +-
10 files changed, 174 insertions(+), 60 deletions(-)
diff --git a/docs/SPEC-EXTENSIONS.md b/docs/SPEC-EXTENSIONS.md
index ce4b4a47..d29933e3 100644
--- a/docs/SPEC-EXTENSIONS.md
+++ b/docs/SPEC-EXTENSIONS.md
@@ -164,8 +164,8 @@ Rules:
- Credential-bearing tier `base_url` values must be HTTPS and cannot point at
loopback, private, link-local, or unrelated hosts unless
`allow_cross_host_auth: true` documents explicit review.
-- Incompatible with `client_pattern: proxy-envelope` and with resource-level
- `base_url` overrides when any tier declares its own `base_url`.
+- Incompatible with `client_pattern: proxy-envelope` and with resource- or
+ endpoint-level `base_url` overrides when any tier declares its own `base_url`.
- Tier credential env vars are read from the environment at request time; they
are not serialized into generated config files.
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index fe26eaba..b9f8bc95 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -657,9 +657,9 @@ type configTemplateData struct {
// endpointTemplateData is the data passed to command_endpoint.go.tmpl
// for both top-level resource endpoints and sub-resource endpoints.
-// ResourceBaseURL carries the resource's BaseURL override (or its
-// inherited parent override for sub-resources); the template prepends
-// it to Endpoint.Path so per-resource hosts produce absolute URLs.
+// ResourceBaseURL carries the endpoint's effective BaseURL override. The
+// template prepends it to Endpoint.Path so per-request hosts produce absolute
+// URLs.
type endpointTemplateData struct {
ResourceName string
ResourceBaseURL string
@@ -1732,7 +1732,7 @@ func (g *Generator) renderResourceCommands(promotedResourceNames map[string]bool
asyncInfo, isAsync := g.AsyncJobs[name+"/"+eName]
epData := endpointTemplateData{
ResourceName: name,
- ResourceBaseURL: strings.TrimRight(resource.BaseURL, "/"),
+ ResourceBaseURL: effectiveEndpointBaseURL(resource, endpoint),
EffectivePath: effectiveEndpointPath(resource, endpoint),
EffectiveTier: g.Spec.EffectiveTier(resource, endpoint),
FuncPrefix: name,
@@ -1773,17 +1773,6 @@ func (g *Generator) renderResourceCommands(promotedResourceNames map[string]bool
return fmt.Errorf("rendering sub-parent %s/%s: %w", name, subName, err)
}
- // Sub-resources inherit the parent's BaseURL override; an
- // explicit sub_resource.base_url wins. Falls through to the
- // spec-level BaseURL when both are empty. Trailing slash is
- // trimmed so the template's `path := <base><endpoint.path>`
- // concat doesn't produce `https://x.com/v1//search` when the
- // override and endpoint path both carry slashes.
- subResourceBaseURL := subResource.BaseURL
- if subResourceBaseURL == "" {
- subResourceBaseURL = resource.BaseURL
- }
- subResourceBaseURL = strings.TrimRight(subResourceBaseURL, "/")
for eName, endpoint := range subResource.Endpoints {
subKey := subName + "/" + eName
asyncInfo, isAsync := g.AsyncJobs[subKey]
@@ -1793,7 +1782,7 @@ func (g *Generator) renderResourceCommands(promotedResourceNames map[string]bool
}
epData := endpointTemplateData{
ResourceName: subName,
- ResourceBaseURL: subResourceBaseURL,
+ ResourceBaseURL: effectiveSubEndpointBaseURL(resource, subResource, endpoint),
EffectivePath: effectiveSubEndpointPath(resource, subResource, endpoint),
EffectiveTier: g.Spec.EffectiveTier(effectiveResource, endpoint),
FuncPrefix: name + "-" + subName,
@@ -2476,34 +2465,36 @@ func (g *Generator) renderPromotedCommandFiles(promotedCommands []PromotedComman
for _, pc := range promotedCommands {
// Look up the full resource to pass sibling endpoints/sub-resources.
// Trim trailing slash on BaseURL so the promoted handler's
- // `path := <Resource.BaseURL><Endpoint.Path>` concat doesn't
+ // `path := <ResourceBaseURL><Endpoint.Path>` concat doesn't
// produce `https://x.com/v1//search`.
resource := g.Spec.Resources[pc.ResourceName]
- resource.BaseURL = strings.TrimRight(resource.BaseURL, "/")
+ resourceBaseURL := effectiveEndpointBaseURL(resource, pc.Endpoint)
promotedData := struct {
- PromotedName string
- ResourceName string
- EndpointName string
- EffectivePath string
- Endpoint spec.Endpoint
- EffectiveTier string
- HasStore bool
- Resource spec.Resource
- FuncPrefix string
- IsReadOnly bool
+ PromotedName string
+ ResourceName string
+ EndpointName string
+ ResourceBaseURL string
+ EffectivePath string
+ Endpoint spec.Endpoint
+ EffectiveTier string
+ HasStore bool
+ Resource spec.Resource
+ FuncPrefix string
+ IsReadOnly bool
*spec.APISpec
}{
- PromotedName: pc.PromotedName,
- ResourceName: pc.ResourceName,
- EndpointName: pc.EndpointName,
- EffectivePath: effectiveEndpointPath(resource, pc.Endpoint),
- Endpoint: pc.Endpoint,
- EffectiveTier: g.Spec.EffectiveTier(resource, pc.Endpoint),
- HasStore: g.VisionSet.Store,
- Resource: resource,
- FuncPrefix: pc.ResourceName,
- IsReadOnly: endpointIsReadCommand(pc.Endpoint, pc.EndpointName),
- APISpec: g.Spec,
+ PromotedName: pc.PromotedName,
+ ResourceName: pc.ResourceName,
+ EndpointName: pc.EndpointName,
+ ResourceBaseURL: resourceBaseURL,
+ EffectivePath: effectiveEndpointPath(resource, pc.Endpoint),
+ Endpoint: pc.Endpoint,
+ EffectiveTier: g.Spec.EffectiveTier(resource, pc.Endpoint),
+ HasStore: g.VisionSet.Store,
+ Resource: resource,
+ FuncPrefix: pc.ResourceName,
+ IsReadOnly: endpointIsReadCommand(pc.Endpoint, pc.EndpointName),
+ APISpec: g.Spec,
}
promotedPath := filepath.Join("internal", "cli", "promoted_"+pc.PromotedName+".go")
if err := g.renderTemplate("command_promoted.go.tmpl", promotedPath, promotedData); err != nil {
@@ -3843,15 +3834,30 @@ func lookupEndpointForTemplate(api *spec.APISpec, ref string) (templateEndpoint,
}
func effectiveEndpointPath(resource spec.Resource, endpoint spec.Endpoint) string {
- return endpointPathWithBase(resource.BaseURL, endpoint.Path)
+ return endpointPathWithBase(effectiveEndpointBaseURL(resource, endpoint), endpoint.Path)
}
func effectiveSubEndpointPath(parent spec.Resource, sub spec.Resource, endpoint spec.Endpoint) string {
- baseURL := sub.BaseURL
+ return endpointPathWithBase(effectiveSubEndpointBaseURL(parent, sub, endpoint), endpoint.Path)
+}
+
+func effectiveEndpointBaseURL(resource spec.Resource, endpoint spec.Endpoint) string {
+ baseURL := endpoint.BaseURL
+ if baseURL == "" {
+ baseURL = resource.BaseURL
+ }
+ return strings.TrimRight(baseURL, "/")
+}
+
+func effectiveSubEndpointBaseURL(parent spec.Resource, sub spec.Resource, endpoint spec.Endpoint) string {
+ baseURL := endpoint.BaseURL
+ if baseURL == "" {
+ baseURL = sub.BaseURL
+ }
if baseURL == "" {
baseURL = parent.BaseURL
}
- return endpointPathWithBase(baseURL, endpoint.Path)
+ return strings.TrimRight(baseURL, "/")
}
func endpointPathWithBase(baseURL, path string) string {
diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index 2a1e87d8..4f371b98 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -6869,6 +6869,43 @@ func TestGenerateSubResourceInheritsParentBaseURL(t *testing.T) {
runGoCommand(t, outputDir, "build", "./...")
}
+func TestGenerateEndpointBaseURLOverrideRoutesToOverrideHost(t *testing.T) {
+ t.Parallel()
+ apiSpec := minimalSpec("endpoint-multihost")
+ apiSpec.BaseURL = "https://api.example.com/v1"
+ apiSpec.Resources["forecast"] = spec.Resource{
+ Description: "Weather forecast",
+ Endpoints: map[string]spec.Endpoint{
+ "now": {Method: "GET", Path: "/forecast", Description: "Current"},
+ "geocode": {
+ Method: "GET",
+ Path: "/search",
+ BaseURL: "https://geocoding-api.example.com/v1",
+ Description: "Geocode",
+ },
+ },
+ }
+
+ outputDir := filepath.Join(t.TempDir(), naming.CLI(apiSpec.Name))
+ gen := New(apiSpec, outputDir)
+ require.NoError(t, gen.Generate())
+
+ geoHandler, err := os.ReadFile(filepath.Join(outputDir, "internal", "cli", "forecast_geocode.go"))
+ require.NoError(t, err)
+ assert.Contains(t, string(geoHandler), `path := "https://geocoding-api.example.com/v1/search"`,
+ "endpoint override must emit the absolute URL into path")
+
+ fcHandler, err := os.ReadFile(filepath.Join(outputDir, "internal", "cli", "forecast_now.go"))
+ require.NoError(t, err)
+ assert.Contains(t, string(fcHandler), `path := "/forecast"`,
+ "sibling endpoint without override must keep the relative path")
+
+ mcpTools, err := os.ReadFile(filepath.Join(outputDir, "internal", "mcp", "tools.go"))
+ require.NoError(t, err)
+ assert.Contains(t, string(mcpTools), `makeAPIHandler("GET", "https://geocoding-api.example.com/v1/search"`,
+ "typed MCP endpoint tool must use the endpoint override URL")
+}
+
// TestGenerateNoResourceBaseURLOverrideByteCompat — specs without any
// per-resource BaseURL override must regenerate without the
// isAbsoluteURL helper or the absolute-URL branch in client.do().
diff --git a/internal/generator/templates/client.go.tmpl b/internal/generator/templates/client.go.tmpl
index 0140677c..4e4268eb 100644
--- a/internal/generator/templates/client.go.tmpl
+++ b/internal/generator/templates/client.go.tmpl
@@ -489,7 +489,7 @@ func (c *Client) do(method, path string, params map[string]string, body any, hea
{{- else}}
{{- if .EndpointTemplateVars}}
{{- if .HasResourceBaseURLOverride}}
- // Per-resource BaseURL overrides flow through as absolute paths
+ // Resource and endpoint BaseURL overrides flow through as absolute paths
// (https:// or http://); buildURL still substitutes any
// {placeholder} markers, but the relative-path concat with
// c.BaseURL must be skipped for absolute paths.
@@ -511,7 +511,7 @@ func (c *Client) do(method, path string, params map[string]string, body any, hea
{{- end}}
{{- else}}
{{- if .HasResourceBaseURLOverride}}
- // Per-resource BaseURL overrides flow through as absolute paths
+ // Resource and endpoint BaseURL overrides flow through as absolute paths
// (https:// or http://); the resource template emits the full URL
// in `path` and we skip the c.BaseURL concat for those.
var targetURL string
@@ -1185,8 +1185,8 @@ func (c *Client) refreshAccessToken() error {
{{- end}}
{{if .HasResourceBaseURLOverride -}}
-// isAbsoluteURL reports whether path is already a full URL (per-resource
-// BaseURL override emits the full URL into path; the relative-path
+// isAbsoluteURL reports whether path is already a full URL (resource or
+// endpoint BaseURL override emits the full URL into path; the relative-path
// concat with c.BaseURL must be skipped for those calls).
func isAbsoluteURL(path string) bool {
return strings.HasPrefix(path, "https://") || strings.HasPrefix(path, "http://")
diff --git a/internal/generator/templates/command_endpoint.go.tmpl b/internal/generator/templates/command_endpoint.go.tmpl
index e7505acc..27e5ccb5 100644
--- a/internal/generator/templates/command_endpoint.go.tmpl
+++ b/internal/generator/templates/command_endpoint.go.tmpl
@@ -120,7 +120,7 @@ func new{{camel .FuncPrefix}}{{camel .EndpointName}}Cmd(flags *rootFlags) *cobra
c = c.WithTier({{printf "%q" .EffectiveTier}})
{{- end}}
- path := "{{.ResourceBaseURL}}{{.Endpoint.Path}}"{{/* ResourceBaseURL is empty unless the resource declares an override; when set it makes path absolute and the client skips the c.BaseURL concat. */}}
+ path := "{{.ResourceBaseURL}}{{.Endpoint.Path}}"{{/* ResourceBaseURL is empty unless the resource or endpoint declares an override; when set it makes path absolute and the client skips the c.BaseURL concat. */}}
{{- range $i, $p := .Endpoint.Params}}
{{- if .Positional}}
{{- if gt $i 0}}
diff --git a/internal/generator/templates/command_promoted.go.tmpl b/internal/generator/templates/command_promoted.go.tmpl
index dc13877c..3a9bab1e 100644
--- a/internal/generator/templates/command_promoted.go.tmpl
+++ b/internal/generator/templates/command_promoted.go.tmpl
@@ -88,7 +88,7 @@ func new{{camel .PromotedName}}PromotedCmd(flags *rootFlags) *cobra.Command {
c = c.WithTier({{printf "%q" .EffectiveTier}})
{{- end}}
- path := "{{.Resource.BaseURL}}{{.Endpoint.Path}}"{{/* Resource.BaseURL is empty unless the resource declares an override; when set it makes path absolute and the client skips the c.BaseURL concat. */}}
+ path := "{{.ResourceBaseURL}}{{.Endpoint.Path}}"{{/* ResourceBaseURL is empty unless the resource or endpoint declares an override; when set it makes path absolute and the client skips the c.BaseURL concat. */}}
{{- range $i, $p := .Endpoint.Params}}
{{- if .Positional}}
if len(args) < {{add $i 1}} {
diff --git a/internal/openapi/parser.go b/internal/openapi/parser.go
index f0e8b4f3..e7f75398 100644
--- a/internal/openapi/parser.go
+++ b/internal/openapi/parser.go
@@ -1671,6 +1671,7 @@ func mapResources(doc *openapi3.T, out *spec.APISpec, basePath string) {
endpoint := spec.Endpoint{
Method: strings.ToUpper(method),
Path: path,
+ BaseURL: operationServerBaseURL(out.BaseURL, pathItem, op),
Description: description,
Params: params,
Body: body,
@@ -1722,6 +1723,24 @@ func mapResources(doc *openapi3.T, out *spec.APISpec, basePath string) {
filterGlobalParams(out.Resources)
}
+func operationServerBaseURL(specBaseURL string, pathItem *openapi3.PathItem, op *openapi3.Operation) string {
+ var servers openapi3.Servers
+ if pathItem != nil {
+ servers = pathItem.Servers
+ }
+ if op != nil && op.Servers != nil {
+ servers = *op.Servers
+ }
+ if len(servers) == 0 || servers[0] == nil {
+ return ""
+ }
+ baseURL, _ := resolveServerURL(servers[0])
+ if baseURL == "" || baseURL == strings.TrimRight(specBaseURL, "/") {
+ return ""
+ }
+ return baseURL
+}
+
// operationAllowsAnonymous checks whether an operation can be called without
// authentication. Returns true when:
// - The operation has security: [] (explicit opt-out)
diff --git a/internal/openapi/parser_test.go b/internal/openapi/parser_test.go
index fd6c5e8c..3c43e5b0 100644
--- a/internal/openapi/parser_test.go
+++ b/internal/openapi/parser_test.go
@@ -4100,6 +4100,47 @@ paths:
assert.Equal(t, "https://global.example.com", parsed.BaseURL)
}
+func TestParseOperationServersBecomeEndpointBaseURLOverrides(t *testing.T) {
+ t.Parallel()
+
+ specYAML := `openapi: "3.0.3"
+info:
+ title: Multi Host Test
+ version: "1.0"
+servers:
+ - url: https://api.open-meteo.com/v1
+paths:
+ /forecast:
+ get:
+ operationId: forecast
+ responses:
+ '200': {description: OK}
+ /search:
+ get:
+ operationId: geocoding
+ servers:
+ - url: https://geocoding-api.open-meteo.com/v1
+ responses:
+ '200': {description: OK}
+`
+ parsed, err := Parse([]byte(specYAML))
+ require.NoError(t, err)
+ assert.Equal(t, "https://api.open-meteo.com/v1", parsed.BaseURL)
+
+ var search spec.Endpoint
+ found := false
+ for _, resource := range parsed.Resources {
+ for _, endpoint := range resource.Endpoints {
+ if endpoint.Path == "/search" {
+ search = endpoint
+ found = true
+ }
+ }
+ }
+ require.True(t, found, "expected /search endpoint to be parsed")
+ assert.Equal(t, "https://geocoding-api.open-meteo.com/v1", search.BaseURL)
+}
+
func TestParseMCPExtensionFromRoot(t *testing.T) {
t.Parallel()
data := []byte(`
diff --git a/internal/spec/spec.go b/internal/spec/spec.go
index 6745d439..8e0cd6ae 100644
--- a/internal/spec/spec.go
+++ b/internal/spec/spec.go
@@ -883,10 +883,9 @@ type Resource struct {
SubResources map[string]Resource `yaml:"sub_resources,omitempty" json:"sub_resources,omitempty"`
}
-// HasResourceBaseURLOverride reports whether any resource (top-level or
-// nested sub-resource) declares a BaseURL override. Used by the client
-// template to gate the absolute-URL detection branch — specs that don't
-// opt in regenerate byte-identically.
+// HasResourceBaseURLOverride reports whether any resource or endpoint declares
+// a BaseURL override. Used by the client template to gate the absolute-URL
+// detection branch — specs that don't opt in regenerate byte-identically.
func (s *APISpec) HasResourceBaseURLOverride() bool {
if s == nil {
return false
@@ -903,6 +902,11 @@ func resourceHasBaseURLOverride(resource Resource) bool {
if resource.BaseURL != "" {
return true
}
+ for _, endpoint := range resource.Endpoints {
+ if endpoint.BaseURL != "" {
+ return true
+ }
+ }
for _, sub := range resource.SubResources {
if resourceHasBaseURLOverride(sub) {
return true
@@ -914,6 +918,7 @@ func resourceHasBaseURLOverride(resource Resource) bool {
type Endpoint struct {
Method string `yaml:"method" json:"method"`
Path string `yaml:"path" json:"path"`
+ BaseURL string `yaml:"base_url,omitempty" json:"base_url,omitempty"`
Description string `yaml:"description" json:"description"`
Params []Param `yaml:"params" json:"params"`
Body []Param `yaml:"body" json:"body"`
@@ -1578,7 +1583,7 @@ func (s *APISpec) Validate() error {
return err
}
if s.ClientPattern == "proxy-envelope" && s.HasResourceBaseURLOverride() {
- return fmt.Errorf("resource base_url overrides are incompatible with client_pattern=proxy-envelope; the proxy POSTs every request to the spec-level BaseURL, so per-resource overrides would be silently ignored")
+ return fmt.Errorf("resource or endpoint base_url overrides are incompatible with client_pattern=proxy-envelope; the proxy POSTs every request to the spec-level BaseURL, so per-request overrides would be silently ignored")
}
for name, r := range s.Resources {
if len(r.Endpoints) == 0 && len(r.SubResources) == 0 {
@@ -1854,7 +1859,7 @@ func validateTierRouting(s *APISpec) error {
}
}
if anyTierBaseURL && s.HasResourceBaseURLOverride() {
- return fmt.Errorf("resource base_url overrides are incompatible with tier_routing tier base_url overrides; choose one routing source")
+ return fmt.Errorf("resource or endpoint base_url overrides are incompatible with tier_routing tier base_url overrides; choose one routing source")
}
for name, resource := range s.Resources {
if err := validateTierRoutingResource(s, name, resource, "", ""); err != nil {
@@ -2026,6 +2031,10 @@ func validateTierRoutingResource(s *APISpec, resourcePath string, resource Resou
effectiveResource := resource
effectiveResource.Tier = resourceTier
for endpointName, endpoint := range resource.Endpoints {
+ endpointBaseURL := strings.TrimSpace(endpoint.BaseURL)
+ if endpointBaseURL == "" {
+ endpointBaseURL = resourceBaseURL
+ }
tierName, tier, ok := s.EffectiveTierConfig(effectiveResource, endpoint)
if tierName == "" {
continue
@@ -2036,9 +2045,9 @@ func validateTierRoutingResource(s *APISpec, resourcePath string, resource Resou
if endpoint.NoAuth && tierAuthRequiresCredential(tier.Auth) {
return fmt.Errorf("resource %q endpoint %q declares no_auth but tier %q requires credentials", resourcePath, endpointName, tierName)
}
- if tierAuthRequiresCredential(tier.Auth) && strings.TrimSpace(tier.BaseURL) == "" && resourceBaseURL != "" {
+ if tierAuthRequiresCredential(tier.Auth) && strings.TrimSpace(tier.BaseURL) == "" && endpointBaseURL != "" {
resourceTier := tier
- resourceTier.BaseURL = resourceBaseURL
+ resourceTier.BaseURL = endpointBaseURL
if err := validateCredentialTierBaseURL(tierName, resourceTier, s.BaseURL); err != nil {
return fmt.Errorf("resource %q endpoint %q: %w", resourcePath, endpointName, err)
}
diff --git a/skills/printing-press/references/spec-format.md b/skills/printing-press/references/spec-format.md
index 5425986a..65b98995 100644
--- a/skills/printing-press/references/spec-format.md
+++ b/skills/printing-press/references/spec-format.md
@@ -37,6 +37,7 @@ resources: # map[string]Resource (REQUIRED: at least one
list: # endpoint key becomes subcommand: users list
method: GET # string (REQUIRED) must be one of GET | POST | PUT | DELETE
path: "/users" # string (REQUIRED) API path; supports {param} placeholders
+ base_url: "https://search.example.com/v1" # string optional override for this endpoint only
description: "List users" # string endpoint help text
params: # []Param query/path parameters
- name: limit # string upstream wire key; request serialization always uses this value
@@ -259,7 +260,8 @@ Validation in `spec.Validate()` enforces:
- every resource must have at least one endpoint
- every endpoint must have both `method` and `path`
- `resources.<name>.base_url` is allowed for resources that live on another host; sub-resources inherit the parent override unless they set their own `base_url`
-- resource `base_url` overrides cannot be combined with `client_pattern: proxy-envelope`, because proxy-envelope clients POST every request to the root `base_url`
+- `resources.<name>.endpoints.<name>.base_url` is allowed for a single endpoint that lives on another host; it wins over resource and sub-resource overrides
+- resource or endpoint `base_url` overrides cannot be combined with `client_pattern: proxy-envelope`, because proxy-envelope clients POST every request to the root `base_url`
- `flag_name` and `aliases` must be lowercase kebab-case, non-empty when present, and collision-free within their command surface
## 5. Common Mistakes
@@ -272,7 +274,7 @@ These commonly cause generation/build failures or incorrect CLI behavior:
- Defining `body` params on `GET` endpoints (allowed in YAML, but ignored by GET command generation)
- Forgetting `positional: true` for params used in `/{path_placeholders}`
- Using parameter types outside supported scalar set: `string`, `int`, `bool`, `float`
-- Putting a website/feed host in the root `base_url` just to make one resource work; keep the root API host as the default and use a resource `base_url` override for the outlier surface
+- Putting a website/feed host in the root `base_url` just to make one endpoint work; keep the root API host as the default and use a resource or endpoint `base_url` override for the outlier surface
## 5. Type Mapping
← dfb879de chore(main): release 4.2.1 (#805)
·
back to Cli Printing Press
·
Reject stale publish manifests before packaging (#836) 5155331c →