ParseModelIdentifier parses a model identifier string into its components. The identifier format follows the ABNF grammar in Section 4.1 of the spec. Returns a non-nil *ParsedModelIdentifier on success. Returns an error (satisfying V-MAF-001 and V-MAF-006) on syntax violations.
(s string)
| 160 | // Returns a non-nil *ParsedModelIdentifier on success. |
| 161 | // Returns an error (satisfying V-MAF-001 and V-MAF-006) on syntax violations. |
| 162 | func ParseModelIdentifier(s string) (*ParsedModelIdentifier, error) { |
| 163 | modelIdentifierLog.Printf("Parsing model identifier: %q", s) |
| 164 | if s == "" { |
| 165 | return nil, errors.New("model identifier must not be empty") |
| 166 | } |
| 167 | |
| 168 | // Split on the first "?" to separate base from query string. |
| 169 | base, rawQuery, _ := strings.Cut(s, "?") |
| 170 | |
| 171 | p := &ParsedModelIdentifier{ |
| 172 | Raw: s, |
| 173 | Base: base, |
| 174 | Params: map[string]string{}, |
| 175 | } |
| 176 | |
| 177 | // ── Validate base identifier ───────────────────────────────────────────── |
| 178 | if strings.Contains(base, "/") { |
| 179 | // Provider-scoped or glob pattern. |
| 180 | provider, modelPart, _ := strings.Cut(base, "/") |
| 181 | |
| 182 | if err := validateProviderToken(provider); err != nil { |
| 183 | modelIdentifierLog.Printf("Invalid provider token %q: %v", provider, err) |
| 184 | return nil, err |
| 185 | } |
| 186 | |
| 187 | p.Provider = provider |
| 188 | |
| 189 | if strings.Contains(modelPart, "*") { |
| 190 | // Glob pattern. |
| 191 | modelIdentifierLog.Printf("Parsing as glob pattern: provider=%q model-glob=%q", provider, modelPart) |
| 192 | if err := validateModelGlobToken(modelPart); err != nil { |
| 193 | return nil, err |
| 194 | } |
| 195 | p.ModelToken = modelPart |
| 196 | p.IsGlob = true |
| 197 | } else { |
| 198 | // Exact provider-scoped name. |
| 199 | modelIdentifierLog.Printf("Parsing as provider-scoped: provider=%q model=%q", provider, modelPart) |
| 200 | if err := validateModelToken(modelPart); err != nil { |
| 201 | return nil, err |
| 202 | } |
| 203 | p.ModelToken = modelPart |
| 204 | } |
| 205 | } else { |
| 206 | // Bare name. |
| 207 | modelIdentifierLog.Printf("Parsing as bare name: %q", base) |
| 208 | if err := validateBareName(base); err != nil { |
| 209 | return nil, err |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | // ── Parse and validate query string ───────────────────────────────────── |
| 214 | if rawQuery != "" { |
| 215 | modelIdentifierLog.Printf("Parsing query string: %q", rawQuery) |
| 216 | params, err := parseQueryString(rawQuery) |
| 217 | if err != nil { |
| 218 | return nil, err |
| 219 | } |