fix(go,mcp,php,java): declare config validation examples and fix the patterns they expose

2Y
11h ago
+540-12

Stacked on #152, which adds ValidationExamples and TestValidationRegexes. Review after #152; this PR will be retargeted to main once #152 merges.

Why

#152 lets a config field in config.ts list accepted and rejected values next to its ValidationRegex, and checks them in TestValidationRegexes. Only the Ruby module field uses it so far. A default-value check cannot catch a pattern that rejects valid input, which is how the Ruby regression went unnoticed, and the same gap exists for every other target.

Writing examples for the remaining fields surfaced two patterns that reject every or many valid values:

  • Go retractions is a list of {version, comment} objects, but its ValidationRegex matches a single semantic version. The Speakeasy CLI checks a field's pattern against the value formatted with %v, so a configured list arrives as [map[comment:Published accidentally version:v1.0.0]] and always fails. The field cannot be used through the CLI, although the generator renders it into the retract block of go.mod.
  • MCP mcpName only allowed lowercase letters and digits per namespace segment, and lowercase letters, digits and hyphens in the server name. The MCP registry's server.json schema (the 2025-12-11 version the generated server.json declares) accepts ^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$, so registry names such as io.github.acme-labs/petstore (a GitHub owner with a hyphen) were rejected.

What changed

  • Examples (test-only, no pattern changes): ValidationExamples on 69 fields across common/common and every target with structured patterns (cli, csharp, go, javav2, mcp-typescript, php, postman, pythonv2, ruby, terraform, typescriptv2, unity): names, identifiers, package and module paths, versions, URLs, durations, and numbers. Fields whose pattern only lists fixed values, such as ^(true|false)$ or ^(net8\.0|net10\.0)$, have no examples; the pattern already is the list. Values are generic (acme, example.com). Fields that share a pattern use the same examples (for example every version, maxMethodParams, and defaultErrorName); config.ts files run as standalone scripts and cannot import shared constants.

  • Go retractions: the ValidationRegex and ValidationMessage are removed from the field. No generator-side check replaces them: Generator.validateConfig runs whenever the generator loads gen.yaml, and templateRetractions already accepts single versions, ranges such as [v1.0.0, v1.9.9], and items keyed Version, and skips items without a version. A stricter check there would stop generation for configurations that render a valid go.mod today. An invalid version still fails when the go command loads the generated go.mod. Changeset: core / go patch.

  • MCP mcpName: pattern replaced with the registry's, and the message and description now describe the accepted shape (a namespace and a server name separated by one slash, the namespace usually reverse-DNS), since names such as acme/petstore are valid. The registry's 3 to 200 character length limit is not enforced: Go's regexp has no lookahead to combine it with the pattern. The value is only written to package.json and server.json. Changeset: core / mcp-typescript patch.

  • PHP packageName: the pattern had no closing $, so any value starting with a valid Composer name passed, such as acme/petstoreSDK or acme/petstore sdk. The value is written unchanged to name in the generated composer.json, and composer install fails on such names. The pattern is now Composer's own name pattern from its JSON schema (^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$), which matches the same names as before and is anchored. Changeset: core / php patch.

  • Java githubURL links: publishing.gradle prepends https:// to the value and appends .git for the SCM connection. Values the unanchored pattern already accepts, such as https://github.com/acme/acme-java, www.github.com/acme/acme-java or github.com/acme/acme-java.git, rendered as https://https://github.com/... or scm:git:ssh://git@github.com/acme/acme-java.git.git. A leading http(s):// and www. and a trailing / and .git are now stripped, ignoring case, before the links are written; a non-string value is converted to a string first, so it renders as before instead of failing. The pattern is unchanged, and github.com/owner/repo renders exactly as before, so the review SDK does not change. Changeset: core / javav2 patch.

The Go, MCP and Java fixes only widen what is accepted or correct the rendered output. The generator's own validateConfig is unchanged, so no configuration that passes CLI validation or generates today stops doing so. The PHP fix narrows it, but only by names that Composer already rejects.

Not changed here

Writing examples also showed patterns that accept values they should reject. Tightening a pattern makes existing gen.yaml files that rely on the looser behaviour fail validation, and when speakeasyVersion is latest the CLI then reruns with the last version that succeeded, so those SDKs stop receiving generator updates. Each of these needs its own decision, so the examples above avoid the affected values:

  • javav2 githubURL (github\.com\/[a-zA-z\d_-]+?\/.+) has no anchors, and A-z also matches [, \, ], ^, _ and a backtick, so values such as ssh://git@github.com/acme/acme-java or see github.com/acme/acme-java pass. The rendered POM links are now normalized (see above); tightening the pattern would reject values that generate today.
  • javav2 templateVersion (v2) has no anchors, so any value containing v2 passes. The gen.yaml value does not select the template (the field's default does) and the generator overwrites it with the template's version, so a value such as v2.0 generates normally today; tightening would only reject it.
  • The POM scm.url is written without a scheme (github.com/owner/repo) for every value. Fixing it changes the generated publishing.gradle of every published Java SDK, so it is left for a separate change.
  • cli packageName (^[\w\d.\-_/]+$) accepts a leading or trailing slash, such as /acme or acme/, which is not a valid Go module path.
  • typescriptv2 and mcp-typescript packageName (^[\w\d._@\/-]+$) accept values npm rejects, such as @acme (scope without a name) or acme@sdk.
  • baseErrorName ("Must start with a capital letter") and go sdkPackageName ("Must start with letter") accept a leading digit or underscore. The generator sanitizes both names (a leading digit is spelled out), so generated code is not affected.
  • javav2 groupID ("Must start with a letter") accepts a leading digit or underscore, and terraform packageName and providerTypeNameOverride ("Letters, numbers, or - only") accept _.
  • csharp and unity packageName reject - although NuGet package IDs allow it; the value is also used as the root namespace, where - is invalid, so the examples list Acme-Sdk as invalid.

Testing

  • go test ./pkg/templates -run TestValidationRegexes -count=1: passes; examples are read for all 70 fields (69 here plus Ruby module from #152).
  • With the previous PHP packageName pattern, the new invalid examples acme/petstoreSDK, acme/petstore sdk and acme/petstore---sdk match. A root composer.json named acme/petstoreSDK, acme/petstore sdk or acme/petstore! fails composer validate and composer install (Composer 2.10.3) with name : Does not match the regex pattern.
  • cmd/generate -l java -p with githubURL: github.com/owner/repo writes the same three POM links as before; with https://www.github.com/acme/acme-java.git/ or HTTPS://WWW.github.com/acme/acme-java.GIT it writes https://github.com/acme/acme-java, github.com/acme/acme-java and scm:git:ssh://git@github.com/acme/acme-java.git. githubURL: 123 renders https://123 as before.
  • With the previous mcpName pattern, the new valid examples io.github.acme-labs/petstore and io.github.Acme/petstore_mcp.v2 fail.
  • cmd/generate for go with retractions set to {version: v1.0.0, comment: Published accidentally}, {version: "[v1.1.0, v1.1.5]"}, and {Version: v1.2.0} generates a retract block with all three, which go mod edit -json parses. A go.mod retracting 1.0.0 or v2.0.0+build.5 fails go list -m.
  • pkg/generate/java_publishing_links_test.go generates a published Java SDK for canonical, scheme/www./.git/trailing-slash, uppercase and numeric githubURL values and asserts the three rendered links. With the normalization removed, made case-sensitive, or without the string conversion, the matching cases fail.
  • TestListFieldsHaveNoValidationRegex asserts that Go retractions has no ValidationRegex; restoring a version pattern on it fails. List fields with a default are already caught by the default value check.
  • Boundary examples: Composer acme/petstore--sdk (two hyphens) is valid, and MCP io.github.acme/2petstore (server name starting with a digit) is valid.
  • go test ./pkg/generate -count=1, go test ./pkg/templates -count=1, make lint (includes TypeScript checks for every template): pass.
  • Generated output does not change, so no review SDKs are regenerated.
  • Not run: end-to-end speakeasy run, which needs a CLI build bundling this generator.

Public-safety check

  • This change contains no credentials, customer documents, private repository URLs, private filesystem paths, or unredacted private logs.
  • Title, body, comments, and commit messages name no customers or customer-derived identifiers, private paths or trackers, or workflow provenance, and are understandable without private context (.claude/skills/public-repo-communication/SKILL.md).
  • Generated fixtures and review SDK changes are public-safe.
  • I reviewed git diff --check.