fix(go,mcp,php,java): declare config validation examples and fix the patterns they expose
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
retractionsis a list of{version, comment}objects, but itsValidationRegexmatches 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 theretractblock ofgo.mod. - MCP
mcpNameonly allowed lowercase letters and digits per namespace segment, and lowercase letters, digits and hyphens in the server name. The MCP registry'sserver.jsonschema (the2025-12-11version the generatedserver.jsondeclares) accepts^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$, so registry names such asio.github.acme-labs/petstore(a GitHub owner with a hyphen) were rejected.
What changed
-
Examples (test-only, no pattern changes):
ValidationExampleson 69 fields acrosscommon/commonand 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 everyversion,maxMethodParams, anddefaultErrorName);config.tsfiles run as standalone scripts and cannot import shared constants. -
Go
retractions: theValidationRegexandValidationMessageare removed from the field. No generator-side check replaces them:Generator.validateConfigruns whenever the generator loads gen.yaml, andtemplateRetractionsalready accepts single versions, ranges such as[v1.0.0, v1.9.9], and items keyedVersion, and skips items without a version. A stricter check there would stop generation for configurations that render a validgo.modtoday. An invalid version still fails when thegocommand loads the generatedgo.mod. Changeset:core/gopatch. -
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 asacme/petstoreare valid. The registry's 3 to 200 character length limit is not enforced: Go'sregexphas no lookahead to combine it with the pattern. The value is only written topackage.jsonandserver.json. Changeset:core/mcp-typescriptpatch. -
PHP
packageName: the pattern had no closing$, so any value starting with a valid Composer name passed, such asacme/petstoreSDKoracme/petstore sdk. The value is written unchanged tonamein the generatedcomposer.json, andcomposer installfails 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/phppatch. -
Java
githubURLlinks:publishing.gradleprependshttps://to the value and appends.gitfor the SCM connection. Values the unanchored pattern already accepts, such ashttps://github.com/acme/acme-java,www.github.com/acme/acme-javaorgithub.com/acme/acme-java.git, rendered ashttps://https://github.com/...orscm:git:ssh://git@github.com/acme/acme-java.git.git. A leadinghttp(s)://andwww.and a trailing/and.gitare 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, andgithub.com/owner/reporenders exactly as before, so the review SDK does not change. Changeset:core/javav2patch.
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, andA-zalso matches[,\,],^,_and a backtick, so values such asssh://git@github.com/acme/acme-javaorsee github.com/acme/acme-javapass. 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 containingv2passes. 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 asv2.0generates normally today; tightening would only reject it. - The POM
scm.urlis written without a scheme (github.com/owner/repo) for every value. Fixing it changes the generatedpublishing.gradleof every published Java SDK, so it is left for a separate change. - cli
packageName(^[\w\d.\-_/]+$) accepts a leading or trailing slash, such as/acmeoracme/, 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) oracme@sdk. baseErrorName("Must start with a capital letter") and gosdkPackageName("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 terraformpackageNameandproviderTypeNameOverride("Letters, numbers, or - only") accept_. - csharp and unity
packageNamereject-although NuGet package IDs allow it; the value is also used as the root namespace, where-is invalid, so the examples listAcme-Sdkas invalid.
Testing
go test ./pkg/templates -run TestValidationRegexes -count=1: passes; examples are read for all 70 fields (69 here plus Rubymodulefrom #152).- With the previous PHP
packageNamepattern, the new invalid examplesacme/petstoreSDK,acme/petstore sdkandacme/petstore---sdkmatch. A rootcomposer.jsonnamedacme/petstoreSDK,acme/petstore sdkoracme/petstore!failscomposer validateandcomposer install(Composer 2.10.3) withname : Does not match the regex pattern. cmd/generate -l java -pwithgithubURL: github.com/owner/repowrites the same three POM links as before; withhttps://www.github.com/acme/acme-java.git/orHTTPS://WWW.github.com/acme/acme-java.GITit writeshttps://github.com/acme/acme-java,github.com/acme/acme-javaandscm:git:ssh://git@github.com/acme/acme-java.git.githubURL: 123rendershttps://123as before.- With the previous
mcpNamepattern, the new valid examplesio.github.acme-labs/petstoreandio.github.Acme/petstore_mcp.v2fail. cmd/generateforgowithretractionsset to{version: v1.0.0, comment: Published accidentally},{version: "[v1.1.0, v1.1.5]"}, and{Version: v1.2.0}generates aretractblock with all three, whichgo mod edit -jsonparses. Ago.modretracting1.0.0orv2.0.0+build.5failsgo list -m.pkg/generate/java_publishing_links_test.gogenerates a published Java SDK for canonical, scheme/www./.git/trailing-slash, uppercase and numericgithubURLvalues and asserts the three rendered links. With the normalization removed, made case-sensitive, or without the string conversion, the matching cases fail.TestListFieldsHaveNoValidationRegexasserts that Goretractionshas noValidationRegex; 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 MCPio.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.