From b59650aa5decb04592f26394dc64e257d70dd4c8 Mon Sep 17 00:00:00 2001 From: David Newgas Date: Mon, 6 Jul 2026 11:11:25 -0700 Subject: [PATCH 1/9] Add Client generation client.go.tpl and the changes to output/openapi.go in this commit are largely made by an LLM. I added example cases and have closely reviewed the outputted client code. I therefore vouch more for the quality of output than quality of code. This commit is a WIP, I want to do the following: 1. reduce changes to openapi.go 2. Add more helpers to iken and use those in the client --- foji/foji.yaml | 1 + foji/openapi/client.go.tpl | 506 ++++++++++++ output/openapi.go | 127 +++ tests/auth/foji.yaml | 1 + tests/auth/http_client_gen.go | 589 ++++++++++++++ tests/auth/http_handler_gen.go | 82 +- tests/auth/openapi.yaml | 21 + tests/auth/service_gen.go | 7 + tests/csvresponse/foji.yaml | 1 + tests/csvresponse/http_client_gen.go | 133 ++++ tests/example/foji.yaml | 1 + tests/example/http_client_gen.go | 1064 ++++++++++++++++++++++++++ tests/go.mod | 1 + tests/go.sum | 4 +- tests/tests_main.go | 2 +- 15 files changed, 2511 insertions(+), 29 deletions(-) create mode 100644 foji/openapi/client.go.tpl create mode 100644 tests/auth/http_client_gen.go create mode 100644 tests/csvresponse/http_client_gen.go create mode 100644 tests/example/http_client_gen.go diff --git a/foji/foji.yaml b/foji/foji.yaml index a6293b4..9145b57 100644 --- a/foji/foji.yaml +++ b/foji/foji.yaml @@ -97,4 +97,5 @@ processes: 'models_gen.go': foji/openapi/model.go.tpl 'service_gen.go': foji/openapi/service.go.tpl 'handlers_gen.go': foji/openapi/handler.go.tpl + 'client_gen.go': foji/openapi/client.go.tpl '!cmd/serve/main.go': foji/openapi/main.go.tpl diff --git a/foji/openapi/client.go.tpl b/foji/openapi/client.go.tpl new file mode 100644 index 0000000..b525502 --- /dev/null +++ b/foji/openapi/client.go.tpl @@ -0,0 +1,506 @@ +{{- define "toStr" -}} + {{- $e := .RuntimeParams.expr -}} + {{- $t := .RuntimeParams.goType -}} + {{- if .RuntimeParams.isEnum -}}{{ $e }}.String() + {{- else if eq $t "string" -}}{{ $e }} + {{- else if eq $t "time.Time" -}}{{ $e }}.Format(time.RFC3339) + {{- else if eq $t "uuid.UUID" -}}{{ $e }}.String() + {{- else if eq $t "bool" -}}strconv.FormatBool({{ $e }}) + {{- else if eq $t "int" -}}strconv.Itoa({{ $e }}) + {{- else if eq $t "int32" -}}strconv.FormatInt(int64({{ $e }}), 10) + {{- else if eq $t "int16" -}}strconv.FormatInt(int64({{ $e }}), 10) + {{- else if eq $t "int64" -}}strconv.FormatInt({{ $e }}, 10) + {{- else if eq $t "float32" -}}strconv.FormatFloat(float64({{ $e }}), 'f', -1, 32) + {{- else if eq $t "float64" -}}strconv.FormatFloat({{ $e }}, 'f', -1, 64) + {{- else -}}fmt.Sprintf("%v", {{ $e }}) + {{- end -}} +{{- end -}} + +{{- define "authProvided" -}} + {{- $scheme := .RuntimeParams.scheme -}} + {{- $kind := $.SecuritySchemeKind $scheme -}} + {{- if eq $kind "basic" -}}{{ camel $scheme }}Username != "" + {{- else if eq $kind "authCode" -}}{{ camel $scheme }}Token != nil + {{- else if eq $kind "clientCredentials" -}}c.{{ camel $scheme }}Config != nil + {{- else -}}{{ camel $scheme }}Token != "" + {{- end -}} +{{- end -}} + +{{- define "authParams" -}} + {{- $scheme := .RuntimeParams.scheme -}} + {{- $kind := $.SecuritySchemeKind $scheme -}} + {{- if eq $kind "basic" }} {{ camel $scheme }}Username string, {{ camel $scheme }}Password string, + {{- else if eq $kind "authCode" }} {{ camel $scheme }}Token *oauth2.Token, + {{- else if eq $kind "clientCredentials" }} + {{- else }} {{ camel $scheme }}Token string, + {{- end -}} +{{- end -}} + +{{- define "clientMethodSignature"}} + {{- $path := .RuntimeParams.path -}} + {{- $op := .RuntimeParams.op -}} + {{- $package := .RuntimeParams.package -}} + {{- $body := .GetRequestBody $op -}} + {{- range $scheme := $.OpSecuritySchemes $op }}{{ template "authParams" ($.WithParams "scheme" $scheme) }}{{- end }} + {{- range $param := $.OpParams $path $op -}} + {{- $name := print $op.OperationID " " $param.Value.Name -}} + {{- if notEmpty $param.Ref }}{{ $name = trimPrefix "#/components/parameters/" $param.Ref }}{{ end -}} + {{ goToken (camel $param.Value.Name) -}} + {{- if $.ParamIsOptionalType $param }} *{{ end }} {{ $.GetType $package $name $param.Value.Schema }}, + {{- end -}} + {{- if isNotNil $body}} + {{- $type := $.GetType $package (print $op.OperationID " Request") $body.Schema }} body {{ $type -}} + {{- end -}} + ) ( + {{- $response := $.GetOpHappyResponseType $package $op}} + {{- if notEmpty $response}}{{ $.CheckPackage $response $package}}, {{ end }}error) +{{- end -}} + +{{- $package := $.PackageName }} + +// Code generated by foji {{ version }}, template: {{ templateFile }}; DO NOT EDIT. + +package {{ $package }} + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "strconv" + "strings" + "time" +{{- if .HasOAuth2 }} + + "golang.org/x/oauth2" +{{- end }} +{{- if .HasClientCredentials }} + "golang.org/x/oauth2/clientcredentials" +{{- end }} +{{- .CheckAllTypes $package ($.Params.GetWithDefault "Auth" "") -}} +{{- range .GoImports }} + "{{ . }}" +{{- end }} +) + +type Doer interface { + Do(*http.Request) (*http.Response, error) +} + +type ClientOption func(*Client) + +type Client struct { + baseURL string + doer Doer +{{- range $security, $value := .API.Components.SecuritySchemes }} + {{- $kind := $.SecuritySchemeKind $security }} + {{- if eq $kind "basic" }} + {{ camel $security }}Username string + {{ camel $security }}Password string + {{- else if eq $kind "authCode" }} + {{ camel $security }}Config oauth2.Config + {{- else if eq $kind "clientCredentials" }} + {{ camel $security }}Config *clientcredentials.Config + {{- else }} + {{ camel $security }}Token string + {{- end }} +{{- end }} +} + +func NewClient(baseURL string, doer Doer, opts ...ClientOption) *Client { + c := &Client{baseURL: baseURL, doer: doer} + + for _, opt := range opts { + opt(c) + } + + return c +} + +{{- range $security, $value := .API.Components.SecuritySchemes }} + {{- $kind := $.SecuritySchemeKind $security }} + {{- if eq $kind "basic" }} + +func With{{ pascal $security }}Credentials(username, password string) ClientOption { + return func(c *Client) { + c.{{ camel $security }}Username = username + c.{{ camel $security }}Password = password + } +} + {{- else if eq $kind "authCode" }} + +func With{{ pascal $security }}Config(config oauth2.Config) ClientOption { + return func(c *Client) { + c.{{ camel $security }}Config = config + } +} + {{- else if eq $kind "clientCredentials" }} + +func With{{ pascal $security }}Config(config clientcredentials.Config) ClientOption { + return func(c *Client) { + c.{{ camel $security }}Config = &config + } +} + {{- else }} + +func With{{ pascal $security }}Token(token string) ClientOption { + return func(c *Client) { + c.{{ camel $security }}Token = token + } +} + {{- end }} +{{- end }} + +{{- if .HasAuthentication }} +var ErrMissingAuthToken = errors.New("missing auth token") +{{- end }} + +type APIError struct { + StatusCode int + Status string + Body []byte +} + +func (e *APIError) Error() string { + return fmt.Sprintf("%d %s: %s", e.StatusCode, e.Status, string(e.Body)) +} + +{{- range $name, $path := .API.Paths.Map }} + {{- range $verb, $op := $path.Operations }} + {{- $body := $.GetRequestBody $op }} + {{- $opResponse := $.GetOpHappyResponse $package $op }} + {{- $respType := $opResponse.GoType }} + {{- $errRet := "" }} + {{- if notEmpty $respType }}{{ if eq $respType "string" }}{{ $errRet = "\"\", " }}{{ else }}{{ $errRet = "nil, " }}{{ end }}{{ end }} + +{{ goDoc (pascal $op.OperationID) }} +{{- goDoc $op.Summary }} +{{- goDoc $op.Description }} +func (c *Client) {{ pascal $op.OperationID }}(ctx context.Context, + {{- template "clientMethodSignature" ($.WithParams "op" $op "package" $package "path" $path) }} { + {{- range $scheme := $.OpSecuritySchemes $op }} + {{- $kind := $.SecuritySchemeKind $scheme }} + {{- if eq $kind "basic" }} + + if {{ camel $scheme }}Username == "" { + {{ camel $scheme }}Username = c.{{ camel $scheme }}Username + {{ camel $scheme }}Password = c.{{ camel $scheme }}Password + } + {{- else if eq $kind "authCode" }} + {{- else if eq $kind "clientCredentials" }} + {{- else }} + + if {{ camel $scheme }}Token == "" { + {{ camel $scheme }}Token = c.{{ camel $scheme }}Token + } + {{- end }} + {{- end }} + {{- if $.HasAnyAuth $op }} + {{- $groups := $.OpSecurityGroups $op }} + {{- $optional := false }} + {{- range $g := $groups }}{{ if eq (len $g) 0 }}{{ $optional = true }}{{ end }}{{ end }} + {{- if not $optional }} + + if !({{ range $i, $g := $groups }}{{ if $i }} || {{ end }}({{ range $j, $scheme := $g }}{{ if $j }} && {{ end }}{{ template "authProvided" ($.WithParams "scheme" $scheme) }}{{ end }}){{ end }}) { + return {{ $errRet }}ErrMissingAuthToken + } + {{- end }} + {{- end }} + + u := c.baseURL + "{{ $name }}" + {{- $hasQuery := false }} + {{- range $param := $.OpParams $path $op }}{{ if eq $param.Value.In "query" }}{{ $hasQuery = true }}{{ end }}{{ end }} + {{- if $hasQuery }} + queryParams := url.Values{} + {{- end }} + {{- range $param := $.OpParams $path $op }} + {{- $pName := $param.Value.Name }} + {{- $var := goToken (camel $pName) }} + {{- $tName := print $op.OperationID " " $pName }} + {{- if notEmpty $param.Ref }}{{ $tName = trimPrefix "#/components/parameters/" $param.Ref }}{{ end }} + {{- $goType := $.GetType $package $tName $param.Value.Schema }} + {{- $isArray := $param.Value.Schema.Value.Type.Is "array" }} + {{- $isEnum := $.ParamIsEnum $param }} + {{- if $isArray }} + {{- $elemType := $.StripArray $goType }} + {{- $isArrayEnum := $.ParamIsEnumArray $param }} + {{- if eq $param.Value.In "query" }} + for _, v := range {{ $var }} { + queryParams.Add("{{ $pName }}", {{ template "toStr" ($.WithParams "expr" "v" "goType" $elemType "isEnum" $isArrayEnum) }}) + } + {{- end }} + {{- else if $.ParamIsOptionalType $param }} + {{- $deref := print "(*" $var ")" }} + if {{ $var }} != nil { + {{- if eq $param.Value.In "query" }} + queryParams.Set("{{ $pName }}", {{ template "toStr" ($.WithParams "expr" $deref "goType" $goType "isEnum" $isEnum) }}) + {{- end }} + } + {{- else }} + {{- if eq $param.Value.In "path" }} + u = strings.Replace(u, "{{ printf "{%s}" $pName }}", url.PathEscape({{ template "toStr" ($.WithParams "expr" $var "goType" $goType "isEnum" $isEnum) }}), 1) + {{- else if eq $param.Value.In "query" }} + queryParams.Set("{{ $pName }}", {{ template "toStr" ($.WithParams "expr" $var "goType" $goType "isEnum" $isEnum) }}) + {{- end }} + {{- end }} + {{- end }} + {{- if $hasQuery }} + + if len(queryParams) > 0 { + u += "?" + queryParams.Encode() + } + {{- end }} + + {{- if isNotNil $body }} + {{- if $body.IsJson }} + + buf, err := json.Marshal(body) + if err != nil { + return {{ $errRet }}fmt.Errorf("marshal request body: %w", err) + } + + reqBody := bytes.NewReader(buf) + contentType := "application/json" + {{- else if $body.IsText }} + + reqBody := strings.NewReader(body) + contentType := "text/plain" + {{- else if $body.IsForm }} + + form := url.Values{} + {{- range $field, $schemaProp := $.SchemaProperties $body.Schema }} + {{- $fGoType := $.GetType $package (print $op.OperationID " " $field) $schemaProp }} + {{- $fVar := print "body." (pascal $field) }} + {{- $fIsPtr := and (not ($.IsRequiredProperty $field $body.Schema)) $schemaProp.Value.Nullable }} + {{- if $schemaProp.Value.Type.Is "array" }} + for _, v := range {{ $fVar }} { + form.Add("{{ $field }}", {{ template "toStr" ($.WithParams "expr" "v" "goType" ($.StripArray $fGoType) "isEnum" ($.SchemaIsEnumArray $schemaProp)) }}) + } + {{- else if $fIsPtr }} + if {{ $fVar }} != nil { + form.Set("{{ $field }}", {{ template "toStr" ($.WithParams "expr" (print "(*" $fVar ")") "goType" $fGoType "isEnum" ($.SchemaIsEnum $schemaProp)) }}) + } + {{- else }} + form.Set("{{ $field }}", {{ template "toStr" ($.WithParams "expr" $fVar "goType" $fGoType "isEnum" ($.SchemaIsEnum $schemaProp)) }}) + {{- end }} + {{- end }} + + reqBody := strings.NewReader(form.Encode()) + contentType := "application/x-www-form-urlencoded" + {{- else if $body.IsMultipartForm }} + + var bodyBuf bytes.Buffer + + mw := multipart.NewWriter(&bodyBuf) + {{- range $field, $schemaProp := $.SchemaProperties $body.Schema }} + {{- $fGoType := $.GetType $package (print $op.OperationID " " $field) $schemaProp }} + {{- $fVar := print "body." (pascal $field) }} + {{- $fRequired := $.IsRequiredProperty $field $body.Schema }} + {{- $fIsPtr := and (not $fRequired) $schemaProp.Value.Nullable }} + {{- if eq $fGoType "forms.File" }} + {{ if not $fRequired }}if {{ $fVar }}.File != nil { {{ end -}} + { + part, err := mw.CreateFormFile("{{ $field }}", {{ $fVar }}.Filename) + if err != nil { + return {{ $errRet }}fmt.Errorf("multipart file {{ $field }}: %w", err) + } + + if _, err := io.Copy(part, {{ $fVar }}.File); err != nil { + return {{ $errRet }}fmt.Errorf("multipart file {{ $field }}: %w", err) + } + } + {{- if not $fRequired }} }{{ end }} + {{- else if $schemaProp.Value.Type.Is "array" }} + for _, v := range {{ $fVar }} { + if err := mw.WriteField("{{ $field }}", {{ template "toStr" ($.WithParams "expr" "v" "goType" ($.StripArray $fGoType) "isEnum" ($.SchemaIsEnumArray $schemaProp)) }}); err != nil { + return {{ $errRet }}fmt.Errorf("multipart field {{ $field }}: %w", err) + } + } + {{- else if $fIsPtr }} + if {{ $fVar }} != nil { + if err := mw.WriteField("{{ $field }}", {{ template "toStr" ($.WithParams "expr" (print "(*" $fVar ")") "goType" $fGoType "isEnum" ($.SchemaIsEnum $schemaProp)) }}); err != nil { + return {{ $errRet }}fmt.Errorf("multipart field {{ $field }}: %w", err) + } + } + {{- else }} + if err := mw.WriteField("{{ $field }}", {{ template "toStr" ($.WithParams "expr" $fVar "goType" $fGoType "isEnum" ($.SchemaIsEnum $schemaProp)) }}); err != nil { + return {{ $errRet }}fmt.Errorf("multipart field {{ $field }}: %w", err) + } + {{- end }} + {{- end }} + + if err := mw.Close(); err != nil { + return {{ $errRet }}fmt.Errorf("multipart close: %w", err) + } + + reqBody := &bodyBuf + contentType := mw.FormDataContentType() + {{- end }} + {{- end }} + + req, err := http.NewRequestWithContext(ctx, "{{ $verb }}", u, {{ if isNotNil $body }}reqBody{{ else }}http.NoBody{{ end }}) + if err != nil { + return {{ $errRet }}err + } + {{- if isNotNil $body }} + + req.Header.Set("Content-Type", contentType) + {{- end }} + {{- range $param := $.OpParams $path $op }} + {{- $pName := $param.Value.Name }} + {{- $var := goToken (camel $pName) }} + {{- $tName := print $op.OperationID " " $pName }} + {{- if notEmpty $param.Ref }}{{ $tName = trimPrefix "#/components/parameters/" $param.Ref }}{{ end }} + {{- $goType := $.GetType $package $tName $param.Value.Schema }} + {{- $isEnum := $.ParamIsEnum $param }} + {{- if eq $param.Value.In "header" }} + {{- if $.ParamIsOptionalType $param }} + + if {{ $var }} != nil { + req.Header.Set("{{ $pName }}", {{ template "toStr" ($.WithParams "expr" (print "(*" $var ")") "goType" $goType "isEnum" $isEnum) }}) + } + {{- else }} + + req.Header.Set("{{ $pName }}", {{ template "toStr" ($.WithParams "expr" $var "goType" $goType "isEnum" $isEnum) }}) + {{- end }} + {{- else if eq $param.Value.In "cookie" }} + {{- if $.ParamIsOptionalType $param }} + + if {{ $var }} != nil { + req.AddCookie(&http.Cookie{Name: "{{ $pName }}", Value: {{ template "toStr" ($.WithParams "expr" (print "(*" $var ")") "goType" $goType "isEnum" $isEnum) }}}) + } + {{- else }} + + req.AddCookie(&http.Cookie{Name: "{{ $pName }}", Value: {{ template "toStr" ($.WithParams "expr" $var "goType" $goType "isEnum" $isEnum) }}}) + {{- end }} + {{- end }} + {{- end }} + + {{- $opHasOAuth := false }} + {{- range $scheme := $.OpSecuritySchemes $op }}{{ $k := $.SecuritySchemeKind $scheme }}{{ if or (eq $k "authCode") (eq $k "clientCredentials") }}{{ $opHasOAuth = true }}{{ end }}{{ end }} + {{- range $scheme := $.OpSecuritySchemes $op }} + {{- $s := index $.API.Components.SecuritySchemes $scheme }} + {{- $kind := $.SecuritySchemeKind $scheme }} + {{- if eq $kind "bearer" }} + + if {{ camel $scheme }}Token != "" { + req.Header.Set("Authorization", "Bearer "+{{ camel $scheme }}Token) + } + {{- else if eq $kind "basic" }} + + if {{ camel $scheme }}Username != "" { + req.SetBasicAuth({{ camel $scheme }}Username, {{ camel $scheme }}Password) + } + {{- else if eq $kind "apiKey" }} + + if {{ camel $scheme }}Token != "" { + {{- if eq $s.Value.In "header" }} + req.Header.Set("{{ $s.Value.Name }}", {{ camel $scheme }}Token) + {{- else if eq $s.Value.In "query" }} + q := req.URL.Query() + q.Set("{{ $s.Value.Name }}", {{ camel $scheme }}Token) + req.URL.RawQuery = q.Encode() + {{- else if eq $s.Value.In "cookie" }} + req.AddCookie(&http.Cookie{Name: "{{ $s.Value.Name }}", Value: {{ camel $scheme }}Token}) + {{- end }} + } + {{- end }} + {{- end }} + + {{- if $opHasOAuth }} + doer := c.doer + {{- range $scheme := $.OpSecuritySchemes $op }} + {{- $kind := $.SecuritySchemeKind $scheme }} + {{- if eq $kind "authCode" }} + + if {{ camel $scheme }}Token != nil { + doer = c.{{ camel $scheme }}Config.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer), {{ camel $scheme }}Token) + } + {{- else if eq $kind "clientCredentials" }} + + if c.{{ camel $scheme }}Config != nil { + doer = c.{{ camel $scheme }}Config.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer)) + } + {{- end }} + {{- end }} + + resp, err := doer.Do(req) + {{- else }} + + resp, err := c.doer.Do(req) + {{- end }} + if err != nil { + return {{ $errRet }}err + } + {{- if ne $respType "io.Reader" }} + defer resp.Body.Close() + {{- end }} + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + {{- if eq $respType "io.Reader" }} + defer resp.Body.Close() + {{- end }} + errBody, _ := io.ReadAll(resp.Body) + + return {{ $errRet }}&APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + {{- if eq $respType "" }} + + return nil + {{- else if $opResponse.MimeType.IsJson }} + {{- if hasPrefix "*" $respType }} + + var out {{ trimPrefix "*" $respType }} + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return {{ $errRet }}err + } + + return &out, nil + {{- else }} + + var out {{ $respType }} + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return {{ $errRet }}err + } + + return out, nil + {{- end }} + {{- else if eq $respType "io.Reader" }} + + return resp.Body, nil + {{- else if eq $respType "[]byte" }} + + out, err := io.ReadAll(resp.Body) + if err != nil { + return {{ $errRet }}fmt.Errorf("read response: %w", err) + } + + return out, nil + {{- else if hasPrefix "*" $respType }} + + out, err := io.ReadAll(resp.Body) + if err != nil { + return {{ $errRet }}fmt.Errorf("read response: %w", err) + } + + v := {{ trimPrefix "*" $respType }}(out) + + return &v, nil + {{- else }} + + out, err := io.ReadAll(resp.Body) + if err != nil { + return {{ $errRet }}fmt.Errorf("read response: %w", err) + } + + return {{ $respType }}(out), nil + {{- end }} +} + {{- end }} +{{- end }} diff --git a/output/openapi.go b/output/openapi.go index 74be954..2ae5c45 100644 --- a/output/openapi.go +++ b/output/openapi.go @@ -656,6 +656,133 @@ func (o *OpenAPIFileContext) OpSecurity(op *openapi3.Operation) openapi3.Securit return o.API.Security } +// OpSecuritySchemes returns the distinct security scheme names referenced by the operation +// across all of its security requirement groups, sorted for deterministic output. +func (o *OpenAPIFileContext) OpSecuritySchemes(op *openapi3.Operation) []string { + seen := map[string]bool{} + + for _, group := range o.OpSecurity(op) { + for key := range group { + seen[key] = true + } + } + + out := make([]string, 0, len(seen)) + for k := range seen { + out = append(out, k) + } + + slices.Sort(out) + + return out +} + +// SecurityGroupSchemes returns the scheme names of a single security requirement group (the +// schemes that must ALL be satisfied together), sorted for deterministic output. +func (o *OpenAPIFileContext) SecurityGroupSchemes(group openapi3.SecurityRequirement) []string { + out := make([]string, 0, len(group)) + for k := range group { + out = append(out, k) + } + + slices.Sort(out) + + return out +} + +// OpSecurityGroups returns the operation's security requirement groups as sorted scheme-name +// slices (AND within a group, OR across groups), with duplicate groups removed so the generated +// validation does not repeat identical conditions. An empty slice element represents an empty +// requirement (anonymous access permitted). +func (o *OpenAPIFileContext) OpSecurityGroups(op *openapi3.Operation) [][]string { + var out [][]string + + seen := map[string]bool{} + + for _, group := range o.OpSecurity(op) { + schemes := o.SecurityGroupSchemes(group) + + key := strings.Join(schemes, "\x00") + if seen[key] { + continue + } + + seen[key] = true + + out = append(out, schemes) + } + + return out +} + +// SecuritySchemeKind classifies a security scheme for client generation. It returns one of: +// - "basic", "bearer", "apiKey" (the default, which also covers custom x-raw-auth schemes); +// - "clientCredentials" for an OAuth2 scheme that declares a client-credentials flow, which the +// client drives entirely from a clientcredentials.Config (no per-request token); +// - "authCode" for any other OAuth2 flow or an OpenID Connect scheme, which the client drives +// from an oauth2.Config plus a per-request *oauth2.Token. +func (o *OpenAPIFileContext) SecuritySchemeKind(name string) string { + if o.API.Components == nil { + return "" + } + + s := o.API.Components.SecuritySchemes[name] + if s == nil || s.Value == nil { + return "" + } + + switch s.Value.Type { + case "http": + if s.Value.Scheme == "basic" { + return "basic" + } + + return "bearer" + case "oauth2": + if s.Value.Flows != nil && s.Value.Flows.ClientCredentials != nil { + return "clientCredentials" + } + + return "authCode" + case "openIdConnect": + return "authCode" + default: + return "apiKey" + } +} + +// HasOAuth2 reports whether any security scheme uses OAuth2 or OpenID Connect. Both drive the +// request through an oauth2-wrapped HTTP client, so the "golang.org/x/oauth2" package is needed. +func (o *OpenAPIFileContext) HasOAuth2() bool { + if o.API.Components == nil { + return false + } + + for _, s := range o.API.Components.SecuritySchemes { + if s != nil && s.Value != nil && (s.Value.Type == "oauth2" || s.Value.Type == "openIdConnect") { + return true + } + } + + return false +} + +// HasClientCredentials reports whether any security scheme is an OAuth2 client-credentials flow, +// which requires the "golang.org/x/oauth2/clientcredentials" package. +func (o *OpenAPIFileContext) HasClientCredentials() bool { + if o.API.Components == nil { + return false + } + + for name := range o.API.Components.SecuritySchemes { + if o.SecuritySchemeKind(name) == "clientCredentials" { + return true + } + } + + return false +} + func hasAuthorization(security openapi3.SecurityRequirements) bool { for _, ss := range security { for _, scopes := range ss { diff --git a/tests/auth/foji.yaml b/tests/auth/foji.yaml index 65c9eea..b1394c9 100644 --- a/tests/auth/foji.yaml +++ b/tests/auth/foji.yaml @@ -9,5 +9,6 @@ processes: Auth: tests/example.ExampleAuth OpenAPIFile: 'tests/auth/http_handler_gen.go': foji/openapi/handler.go.tpl + 'tests/auth/http_client_gen.go': foji/openapi/client.go.tpl 'tests/auth/model_gen.go': foji/openapi/model.go.tpl 'tests/auth/service_gen.go': foji/openapi/service.go.tpl diff --git a/tests/auth/http_client_gen.go b/tests/auth/http_client_gen.go new file mode 100644 index 0000000..7371b9d --- /dev/null +++ b/tests/auth/http_client_gen.go @@ -0,0 +1,589 @@ +// Code generated by foji (dev build), template: foji/openapi/client.go.tpl; DO NOT EDIT. + +package auth + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + + "github.com/bir/iken/httputil" + "golang.org/x/oauth2" + "golang.org/x/oauth2/clientcredentials" +) + +type Doer interface { + Do(*http.Request) (*http.Response, error) +} + +type ClientOption func(*Client) + +type Client struct { + baseURL string + doer Doer + apiKeyCookieToken string + apiKeyHeaderToken string + apiKeyQueryToken string + basicAuthUsername string + basicAuthPassword string + bearerAuthToken string + oauth2ClientCredentialsExampleConfig *clientcredentials.Config + oauth2ExampleConfig oauth2.Config + openIdconnectConfig oauth2.Config + rawToken string +} + +func NewClient(baseURL string, doer Doer, opts ...ClientOption) *Client { + c := &Client{baseURL: baseURL, doer: doer} + + for _, opt := range opts { + opt(c) + } + + return c +} + +func WithApiKeyCookieToken(token string) ClientOption { + return func(c *Client) { + c.apiKeyCookieToken = token + } +} + +func WithApiKeyHeaderToken(token string) ClientOption { + return func(c *Client) { + c.apiKeyHeaderToken = token + } +} + +func WithApiKeyQueryToken(token string) ClientOption { + return func(c *Client) { + c.apiKeyQueryToken = token + } +} + +func WithBasicAuthCredentials(username, password string) ClientOption { + return func(c *Client) { + c.basicAuthUsername = username + c.basicAuthPassword = password + } +} + +func WithBearerAuthToken(token string) ClientOption { + return func(c *Client) { + c.bearerAuthToken = token + } +} + +func WithOauth2ClientCredentialsExampleConfig(config clientcredentials.Config) ClientOption { + return func(c *Client) { + c.oauth2ClientCredentialsExampleConfig = &config + } +} + +func WithOauth2ExampleConfig(config oauth2.Config) ClientOption { + return func(c *Client) { + c.oauth2ExampleConfig = config + } +} + +func WithOpenIdconnectConfig(config oauth2.Config) ClientOption { + return func(c *Client) { + c.openIdconnectConfig = config + } +} + +func WithRawToken(token string) ClientOption { + return func(c *Client) { + c.rawToken = token + } +} + +var ErrMissingAuthToken = errors.New("missing auth token") + +type APIError struct { + StatusCode int + Status string + Body []byte +} + +func (e *APIError) Error() string { + return fmt.Sprintf("%d %s: %s", e.StatusCode, e.Status, string(e.Body)) +} + +// ListAdminUsers +// List all users (admin only) +// Requires both API key AND bearer token with admin scope +func (c *Client) ListAdminUsers(ctx context.Context, apiKeyHeaderToken string, bearerAuthToken string) ([]User, error) { + if apiKeyHeaderToken == "" { + apiKeyHeaderToken = c.apiKeyHeaderToken + } + + if bearerAuthToken == "" { + bearerAuthToken = c.bearerAuthToken + } + + if !(apiKeyHeaderToken != "" && bearerAuthToken != "") { + return nil, ErrMissingAuthToken + } + + u := c.baseURL + "/admin/users" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + if apiKeyHeaderToken != "" { + req.Header.Set("X-API-Key", apiKeyHeaderToken) + } + + if bearerAuthToken != "" { + req.Header.Set("Authorization", "Bearer "+bearerAuthToken) + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out []User + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return out, nil +} + +// QueryDataWithApiKey +// Query data with API key +// Accepts API key in header, query parameter, or cookie +func (c *Client) QueryDataWithApiKey(ctx context.Context, apiKeyCookieToken string, apiKeyHeaderToken string, apiKeyQueryToken string, rawToken string, query *string) error { + if apiKeyCookieToken == "" { + apiKeyCookieToken = c.apiKeyCookieToken + } + + if apiKeyHeaderToken == "" { + apiKeyHeaderToken = c.apiKeyHeaderToken + } + + if apiKeyQueryToken == "" { + apiKeyQueryToken = c.apiKeyQueryToken + } + + if rawToken == "" { + rawToken = c.rawToken + } + + if !((apiKeyHeaderToken != "") || (apiKeyQueryToken != "") || (apiKeyCookieToken != "") || (rawToken != "")) { + return ErrMissingAuthToken + } + + u := c.baseURL + "/data/query" + queryParams := url.Values{} + if query != nil { + queryParams.Set("query", (*query)) + } + + if len(queryParams) > 0 { + u += "?" + queryParams.Encode() + } + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return err + } + + if apiKeyCookieToken != "" { + req.AddCookie(&http.Cookie{Name: "cookie_name", Value: apiKeyCookieToken}) + } + + if apiKeyHeaderToken != "" { + req.Header.Set("X-API-Key", apiKeyHeaderToken) + } + + if apiKeyQueryToken != "" { + q := req.URL.Query() + q.Set("query_key_name", apiKeyQueryToken) + req.URL.RawQuery = q.Encode() + } + + if rawToken != "" { + req.Header.Set("Authorization", rawToken) + } + + resp, err := c.doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// ListDocuments +// List documents +// Requires basic authentication +func (c *Client) ListDocuments(ctx context.Context, basicAuthUsername string, basicAuthPassword string) error { + if basicAuthUsername == "" { + basicAuthUsername = c.basicAuthUsername + basicAuthPassword = c.basicAuthPassword + } + + if !(basicAuthUsername != "") { + return ErrMissingAuthToken + } + + u := c.baseURL + "/documents" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return err + } + + if basicAuthUsername != "" { + req.SetBasicAuth(basicAuthUsername, basicAuthPassword) + } + + resp, err := c.doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// CreateDocument +// Create document +// Requires API key with bearer token +func (c *Client) CreateDocument(ctx context.Context, apiKeyHeaderToken string, bearerAuthToken string) error { + if apiKeyHeaderToken == "" { + apiKeyHeaderToken = c.apiKeyHeaderToken + } + + if bearerAuthToken == "" { + bearerAuthToken = c.bearerAuthToken + } + + if !(apiKeyHeaderToken != "" && bearerAuthToken != "") { + return ErrMissingAuthToken + } + + u := c.baseURL + "/documents" + + req, err := http.NewRequestWithContext(ctx, "POST", u, http.NoBody) + if err != nil { + return err + } + + if apiKeyHeaderToken != "" { + req.Header.Set("X-API-Key", apiKeyHeaderToken) + } + + if bearerAuthToken != "" { + req.Header.Set("Authorization", "Bearer "+bearerAuthToken) + } + + resp, err := c.doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// Overview +// System overview +// Requires OAuth client credentials +func (c *Client) Overview(ctx context.Context) error { + if !(c.oauth2ClientCredentialsExampleConfig != nil) { + return ErrMissingAuthToken + } + + u := c.baseURL + "/overview" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return err + } + doer := c.doer + + if c.oauth2ClientCredentialsExampleConfig != nil { + doer = c.oauth2ClientCredentialsExampleConfig.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer)) + } + + resp, err := doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// GetDetailedProfile +// Get detailed profile +// Requires OpenID Connect authentication +func (c *Client) GetDetailedProfile(ctx context.Context, openIdconnectToken *oauth2.Token) error { + if !(openIdconnectToken != nil) { + return ErrMissingAuthToken + } + + u := c.baseURL + "/profile/detailed" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return err + } + doer := c.doer + + if openIdconnectToken != nil { + doer = c.openIdconnectConfig.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer), openIdconnectToken) + } + + resp, err := doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// GetProtectedResource +// Access protected resource +// Multiple authentication options: +// 1. OAuth2 with read scope, OR +// 2. API key (header) + Basic auth, OR +// 3. Bearer token + API +// key (cookie) +func (c *Client) GetProtectedResource(ctx context.Context, apiKeyCookieToken string, apiKeyHeaderToken string, basicAuthUsername string, basicAuthPassword string, bearerAuthToken string) error { + if apiKeyCookieToken == "" { + apiKeyCookieToken = c.apiKeyCookieToken + } + + if apiKeyHeaderToken == "" { + apiKeyHeaderToken = c.apiKeyHeaderToken + } + + if basicAuthUsername == "" { + basicAuthUsername = c.basicAuthUsername + basicAuthPassword = c.basicAuthPassword + } + + if bearerAuthToken == "" { + bearerAuthToken = c.bearerAuthToken + } + + if !((apiKeyHeaderToken != "" && basicAuthUsername != "") || (apiKeyCookieToken != "" && bearerAuthToken != "")) { + return ErrMissingAuthToken + } + + u := c.baseURL + "/protected-resource" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return err + } + + if apiKeyCookieToken != "" { + req.AddCookie(&http.Cookie{Name: "cookie_name", Value: apiKeyCookieToken}) + } + + if apiKeyHeaderToken != "" { + req.Header.Set("X-API-Key", apiKeyHeaderToken) + } + + if basicAuthUsername != "" { + req.SetBasicAuth(basicAuthUsername, basicAuthPassword) + } + + if bearerAuthToken != "" { + req.Header.Set("Authorization", "Bearer "+bearerAuthToken) + } + + resp, err := c.doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// GetPublicStatus +// Get API status +// Public endpoint that requires no authentication +func (c *Client) GetPublicStatus(ctx context.Context) (*GetPublicStatusResponse, error) { + u := c.baseURL + "/public/status" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out GetPublicStatusResponse + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// GetCurrentUser +// Get current user +// Requires either API key in header OR bearer token +func (c *Client) GetCurrentUser(ctx context.Context, apiKeyHeaderToken string, bearerAuthToken string, oauth2ExampleToken *oauth2.Token) (*User, error) { + if apiKeyHeaderToken == "" { + apiKeyHeaderToken = c.apiKeyHeaderToken + } + + if bearerAuthToken == "" { + bearerAuthToken = c.bearerAuthToken + } + + if !((apiKeyHeaderToken != "") || (bearerAuthToken != "") || (oauth2ExampleToken != nil)) { + return nil, ErrMissingAuthToken + } + + u := c.baseURL + "/users/me" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + if apiKeyHeaderToken != "" { + req.Header.Set("X-API-Key", apiKeyHeaderToken) + } + + if bearerAuthToken != "" { + req.Header.Set("Authorization", "Bearer "+bearerAuthToken) + } + doer := c.doer + + if oauth2ExampleToken != nil { + doer = c.oauth2ExampleConfig.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer), oauth2ExampleToken) + } + + resp, err := doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out User + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// GetUserProfile +// Get user profile +// Requires bearer token authentication +func (c *Client) GetUserProfile(ctx context.Context, bearerAuthToken string) (*User, error) { + if bearerAuthToken == "" { + bearerAuthToken = c.bearerAuthToken + } + + if !(bearerAuthToken != "") { + return nil, ErrMissingAuthToken + } + + u := c.baseURL + "/users/profile" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + if bearerAuthToken != "" { + req.Header.Set("Authorization", "Bearer "+bearerAuthToken) + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out User + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} diff --git a/tests/auth/http_handler_gen.go b/tests/auth/http_handler_gen.go index 4b8d4c4..e11a476 100644 --- a/tests/auth/http_handler_gen.go +++ b/tests/auth/http_handler_gen.go @@ -28,6 +28,7 @@ type Operations interface { QueryDataWithApiKey(ctx context.Context, user *example.ExampleAuth, query *string) error ListDocuments(ctx context.Context, user *example.ExampleAuth) error CreateDocument(ctx context.Context, user *example.ExampleAuth) error + Overview(ctx context.Context, user *example.ExampleAuth) error GetDetailedProfile(ctx context.Context, user *example.ExampleAuth) error GetProtectedResource(ctx context.Context, user *example.ExampleAuth) error GetPublicStatus(ctx context.Context) (*GetPublicStatusResponse, error) @@ -36,39 +37,41 @@ type Operations interface { } type OpenAPIHandlers struct { - ops Operations - apiKeyCookieAuth RequestAuthenticator - apiKeyHeaderAuth RequestAuthenticator - apiKeyQueryAuth RequestAuthenticator - basicAuthAuth RequestAuthenticator - bearerAuthAuth RequestAuthenticator - oauth2ExampleAuth RequestAuthenticator - openIdconnectAuth RequestAuthenticator - rawAuth RequestAuthenticator - authorize AuthorizeFunc - listAdminUsersSecurity SecurityGroups - queryDataWithApiKeySecurity SecurityGroups - createDocumentSecurity SecurityGroups - getProtectedResourceSecurity SecurityGroups - getCurrentUserSecurity SecurityGroups + ops Operations + apiKeyCookieAuth RequestAuthenticator + apiKeyHeaderAuth RequestAuthenticator + apiKeyQueryAuth RequestAuthenticator + basicAuthAuth RequestAuthenticator + bearerAuthAuth RequestAuthenticator + oauth2ClientCredentialsExampleAuth RequestAuthenticator + oauth2ExampleAuth RequestAuthenticator + openIdconnectAuth RequestAuthenticator + rawAuth RequestAuthenticator + authorize AuthorizeFunc + listAdminUsersSecurity SecurityGroups + queryDataWithApiKeySecurity SecurityGroups + createDocumentSecurity SecurityGroups + getProtectedResourceSecurity SecurityGroups + getCurrentUserSecurity SecurityGroups } type Mux interface { Handle(pattern string, handler http.Handler) } -func RegisterHTTP(ops Operations, r Mux, apiKeyCookieAuth TokenAuthenticator, apiKeyHeaderAuth TokenAuthenticator, apiKeyQueryAuth TokenAuthenticator, basicAuthAuth BasicAuthenticator, bearerAuthAuth TokenAuthenticator, oauth2ExampleAuth RequestAuthenticator, openIdconnectAuth RequestAuthenticator, rawAuth RequestAuthenticator, authorize AuthorizeFunc) *OpenAPIHandlers { +func RegisterHTTP(ops Operations, r Mux, apiKeyCookieAuth TokenAuthenticator, apiKeyHeaderAuth TokenAuthenticator, apiKeyQueryAuth TokenAuthenticator, basicAuthAuth BasicAuthenticator, bearerAuthAuth TokenAuthenticator, oauth2ClientCredentialsExampleAuth RequestAuthenticator, oauth2ExampleAuth RequestAuthenticator, openIdconnectAuth RequestAuthenticator, rawAuth RequestAuthenticator, authorize AuthorizeFunc) *OpenAPIHandlers { s := OpenAPIHandlers{ - ops: ops, - apiKeyCookieAuth: httputil.CookieAuth("cookie_name", apiKeyCookieAuth), - apiKeyHeaderAuth: httputil.HeaderAuth("X-API-Key", apiKeyHeaderAuth), - apiKeyQueryAuth: httputil.QueryAuth("query_key_name", apiKeyQueryAuth), - basicAuthAuth: httputil.BasicAuth(basicAuthAuth), - bearerAuthAuth: httputil.BearerAuth("Authorization", bearerAuthAuth), - oauth2ExampleAuth: oauth2ExampleAuth, - openIdconnectAuth: openIdconnectAuth, - rawAuth: rawAuth, - authorize: authorize, + ops: ops, + apiKeyCookieAuth: httputil.CookieAuth("cookie_name", apiKeyCookieAuth), + apiKeyHeaderAuth: httputil.HeaderAuth("X-API-Key", apiKeyHeaderAuth), + apiKeyQueryAuth: httputil.QueryAuth("query_key_name", apiKeyQueryAuth), + basicAuthAuth: httputil.BasicAuth(basicAuthAuth), + bearerAuthAuth: httputil.BearerAuth("Authorization", bearerAuthAuth), + oauth2ClientCredentialsExampleAuth: oauth2ClientCredentialsExampleAuth, + oauth2ExampleAuth: oauth2ExampleAuth, + openIdconnectAuth: openIdconnectAuth, + rawAuth: rawAuth, + authorize: authorize, } s.listAdminUsersSecurity = SecurityGroups{ @@ -101,6 +104,7 @@ func RegisterHTTP(ops Operations, r Mux, apiKeyCookieAuth TokenAuthenticator, ap r.Handle("GET /data/query", http.HandlerFunc(s.QueryDataWithApiKey)) r.Handle("GET /documents", http.HandlerFunc(s.ListDocuments)) r.Handle("POST /documents", http.HandlerFunc(s.CreateDocument)) + r.Handle("GET /overview", http.HandlerFunc(s.Overview)) r.Handle("GET /profile/detailed", http.HandlerFunc(s.GetDetailedProfile)) r.Handle("GET /protected-resource", http.HandlerFunc(s.GetProtectedResource)) r.Handle("GET /public/status", http.HandlerFunc(s.GetPublicStatus)) @@ -233,6 +237,32 @@ func (h OpenAPIHandlers) CreateDocument(w http.ResponseWriter, r *http.Request) w.WriteHeader(201) } +// Overview +// System overview +// Requires OAuth client credentials +func (h OpenAPIHandlers) Overview(w http.ResponseWriter, r *http.Request) { + var err error + + logctx.SetOperation(r.Context(), "overview") + logctx.AddStrToContext(r.Context(), "op", "overview") + + user, err := h.oauth2ClientCredentialsExampleAuth(r) + if err != nil { + httputil.ErrorHandler(w, r, err) + + return + } + + err = h.ops.Overview(r.Context(), user) + if err != nil { + httputil.ErrorHandler(w, r, err) + + return + } + + w.WriteHeader(200) +} + // GetDetailedProfile // Get detailed profile // Requires OpenID Connect authentication diff --git a/tests/auth/openapi.yaml b/tests/auth/openapi.yaml index 79222fa..9d034eb 100644 --- a/tests/auth/openapi.yaml +++ b/tests/auth/openapi.yaml @@ -80,6 +80,15 @@ components: write:pets: modify pets in your account read:pets: read your pets + Oauth2ClientCredentialsExample: + type: oauth2 + flows: + clientCredentials: + tokenUrl: https://example.com/api/oauth/token + scopes: + write:pets: modify pets in your account + read:pets: read your pets + schemas: User: type: object @@ -245,6 +254,18 @@ paths: '200': description: Detailed user profile + # OAuth Client Credentials + /overview: + get: + operationId: overview + summary: System overview + description: Requires OAuth client credentials + security: + - Oauth2ClientCredentialsExample: [] + responses: + '200': + description: System overview data + # API key variations /data/query: get: diff --git a/tests/auth/service_gen.go b/tests/auth/service_gen.go index f71ffa7..8930459 100644 --- a/tests/auth/service_gen.go +++ b/tests/auth/service_gen.go @@ -45,6 +45,13 @@ func (s *GenService) CreateDocument(ctx context.Context, user *example.ExampleAu return errors.ErrUnsupported } +// Overview +// System overview +// Requires OAuth client credentials +func (s *GenService) Overview(ctx context.Context, user *example.ExampleAuth) error { + return errors.ErrUnsupported +} + // GetDetailedProfile // Get detailed profile // Requires OpenID Connect authentication diff --git a/tests/csvresponse/foji.yaml b/tests/csvresponse/foji.yaml index 6d12117..0d73a23 100644 --- a/tests/csvresponse/foji.yaml +++ b/tests/csvresponse/foji.yaml @@ -9,4 +9,5 @@ processes: Auth: ExampleAuth OpenAPIFile: 'tests/csvresponse/http_handler_gen.go': foji/openapi/handler.go.tpl + 'tests/csvresponse/http_client_gen.go': foji/openapi/client.go.tpl 'tests/csvresponse/model_gen.go': foji/openapi/model.go.tpl diff --git a/tests/csvresponse/http_client_gen.go b/tests/csvresponse/http_client_gen.go new file mode 100644 index 0000000..9b065c7 --- /dev/null +++ b/tests/csvresponse/http_client_gen.go @@ -0,0 +1,133 @@ +// Code generated by foji (dev build), template: foji/openapi/client.go.tpl; DO NOT EDIT. + +package csvresponse + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" +) + +type Doer interface { + Do(*http.Request) (*http.Response, error) +} + +type ClientOption func(*Client) + +type Client struct { + baseURL string + doer Doer + headerAuthToken string +} + +func NewClient(baseURL string, doer Doer, opts ...ClientOption) *Client { + c := &Client{baseURL: baseURL, doer: doer} + + for _, opt := range opts { + opt(c) + } + + return c +} + +func WithHeaderAuthToken(token string) ClientOption { + return func(c *Client) { + c.headerAuthToken = token + } +} + +var ErrMissingAuthToken = errors.New("missing auth token") + +type APIError struct { + StatusCode int + Status string + Body []byte +} + +func (e *APIError) Error() string { + return fmt.Sprintf("%d %s: %s", e.StatusCode, e.Status, string(e.Body)) +} + +// GetByteCsv +func (c *Client) GetByteCsv(ctx context.Context) ([]byte, error) { + u := c.baseURL + "/bytesCSV" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + out, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + + return out, nil +} + +// GetReaderCsv +func (c *Client) GetReaderCsv(ctx context.Context) (io.Reader, error) { + u := c.baseURL + "/readerCSV" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + defer resp.Body.Close() + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return resp.Body, nil +} + +// GetStringCsv +func (c *Client) GetStringCsv(ctx context.Context) (string, error) { + u := c.baseURL + "/stringCSV" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return "", err + } + + resp, err := c.doer.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return "", &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + out, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("read response: %w", err) + } + + return string(out), nil +} diff --git a/tests/example/foji.yaml b/tests/example/foji.yaml index 128a01a..0702b13 100644 --- a/tests/example/foji.yaml +++ b/tests/example/foji.yaml @@ -9,4 +9,5 @@ processes: Auth: ExampleAuth OpenAPIFile: tests/example/http_handler_gen.go: foji/openapi/handler.go.tpl + tests/example/http_client_gen.go: foji/openapi/client.go.tpl tests/example/model_gen.go: foji/openapi/model.go.tpl diff --git a/tests/example/http_client_gen.go b/tests/example/http_client_gen.go new file mode 100644 index 0000000..3b9fa30 --- /dev/null +++ b/tests/example/http_client_gen.go @@ -0,0 +1,1064 @@ +// Code generated by foji (dev build), template: foji/openapi/client.go.tpl; DO NOT EDIT. + +package example + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/bir/iken/httputil" + "github.com/google/uuid" +) + +type Doer interface { + Do(*http.Request) (*http.Response, error) +} + +type ClientOption func(*Client) + +type Client struct { + baseURL string + doer Doer + bearerToken string + customHeaderAuthToken string + headerAuthToken string + jwtToken string + rawToken string +} + +func NewClient(baseURL string, doer Doer, opts ...ClientOption) *Client { + c := &Client{baseURL: baseURL, doer: doer} + + for _, opt := range opts { + opt(c) + } + + return c +} + +func WithBearerToken(token string) ClientOption { + return func(c *Client) { + c.bearerToken = token + } +} + +func WithCustomHeaderAuthToken(token string) ClientOption { + return func(c *Client) { + c.customHeaderAuthToken = token + } +} + +func WithHeaderAuthToken(token string) ClientOption { + return func(c *Client) { + c.headerAuthToken = token + } +} + +func WithJwtToken(token string) ClientOption { + return func(c *Client) { + c.jwtToken = token + } +} + +func WithRawToken(token string) ClientOption { + return func(c *Client) { + c.rawToken = token + } +} + +var ErrMissingAuthToken = errors.New("missing auth token") + +type APIError struct { + StatusCode int + Status string + Body []byte +} + +func (e *APIError) Error() string { + return fmt.Sprintf("%d %s: %s", e.StatusCode, e.Status, string(e.Body)) +} + +// GetExamples +func (c *Client) GetExamples(ctx context.Context) (*Examples, error) { + u := c.baseURL + "/examples" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out Examples + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// GetAuthComplex +func (c *Client) GetAuthComplex(ctx context.Context, headerAuthToken string, jwtToken string) error { + if headerAuthToken == "" { + headerAuthToken = c.headerAuthToken + } + + if jwtToken == "" { + jwtToken = c.jwtToken + } + + if !((headerAuthToken != "") || (jwtToken != "")) { + return ErrMissingAuthToken + } + + u := c.baseURL + "/examples/auth/complex" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return err + } + + if headerAuthToken != "" { + req.Header.Set("Authorization", headerAuthToken) + } + + if jwtToken != "" { + q := req.URL.Query() + q.Set("jwt", jwtToken) + req.URL.RawQuery = q.Encode() + } + + resp, err := c.doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// GetAuthSimple +func (c *Client) GetAuthSimple(ctx context.Context, headerAuthToken string) error { + if headerAuthToken == "" { + headerAuthToken = c.headerAuthToken + } + + if !(headerAuthToken != "") { + return ErrMissingAuthToken + } + + u := c.baseURL + "/examples/auth/simple" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return err + } + + if headerAuthToken != "" { + req.Header.Set("Authorization", headerAuthToken) + } + + resp, err := c.doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// GetAuthSimpleMaybe +func (c *Client) GetAuthSimpleMaybe(ctx context.Context, headerAuthToken string) error { + if headerAuthToken == "" { + headerAuthToken = c.headerAuthToken + } + + u := c.baseURL + "/examples/auth/simple/maybe" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return err + } + + if headerAuthToken != "" { + req.Header.Set("Authorization", headerAuthToken) + } + + resp, err := c.doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// GetAuthSimple2 +func (c *Client) GetAuthSimple2(ctx context.Context, headerAuthToken string) error { + if headerAuthToken == "" { + headerAuthToken = c.headerAuthToken + } + + if !(headerAuthToken != "") { + return ErrMissingAuthToken + } + + u := c.baseURL + "/examples/auth/simple2" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return err + } + + if headerAuthToken != "" { + req.Header.Set("Authorization", headerAuthToken) + } + + resp, err := c.doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// GetAuthSimple2Maybe +func (c *Client) GetAuthSimple2Maybe(ctx context.Context, headerAuthToken string) error { + if headerAuthToken == "" { + headerAuthToken = c.headerAuthToken + } + + u := c.baseURL + "/examples/auth/simple2/maybe" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return err + } + + if headerAuthToken != "" { + req.Header.Set("Authorization", headerAuthToken) + } + + resp, err := c.doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// GetAuthComplexMaybe +func (c *Client) GetAuthComplexMaybe(ctx context.Context, headerAuthToken string, jwtToken string) error { + if headerAuthToken == "" { + headerAuthToken = c.headerAuthToken + } + + if jwtToken == "" { + jwtToken = c.jwtToken + } + + u := c.baseURL + "/examples/complexAuthMaybe" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return err + } + + if headerAuthToken != "" { + req.Header.Set("Authorization", headerAuthToken) + } + + if jwtToken != "" { + q := req.URL.Query() + q.Set("jwt", jwtToken) + req.URL.RawQuery = q.Encode() + } + + resp, err := c.doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// GetComplexSecurity +func (c *Client) GetComplexSecurity(ctx context.Context, bearerToken string, customHeaderAuthToken string, rawToken string) ([]TestInt, error) { + if bearerToken == "" { + bearerToken = c.bearerToken + } + + if customHeaderAuthToken == "" { + customHeaderAuthToken = c.customHeaderAuthToken + } + + if rawToken == "" { + rawToken = c.rawToken + } + + if !((rawToken != "") || (bearerToken != "") || (customHeaderAuthToken != "")) { + return nil, ErrMissingAuthToken + } + + u := c.baseURL + "/examples/complexSecurity" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + if bearerToken != "" { + req.Header.Set("Authorization", "Bearer "+bearerToken) + } + + if customHeaderAuthToken != "" { + req.Header.Set("X-CUSTOM-HEADER", customHeaderAuthToken) + } + + if rawToken != "" { + req.Header.Set("Authorization", rawToken) + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out []TestInt + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return out, nil +} + +// AddForm +func (c *Client) AddForm(ctx context.Context, body AddFormRequest) (*FooBar, error) { + u := c.baseURL + "/examples/form" + + form := url.Values{} + form.Set("f01", strconv.FormatBool(body.F01)) + if body.F01Null != nil { + form.Set("f01Null", strconv.FormatBool((*body.F01Null))) + } + form.Set("f01b", strconv.FormatBool(body.F01B)) + if body.F01BNull != nil { + form.Set("f01bNull", strconv.FormatBool((*body.F01BNull))) + } + form.Set("f02", strconv.FormatInt(int64(body.F02), 10)) + if body.F02Null != nil { + form.Set("f02Null", strconv.FormatInt(int64((*body.F02Null)), 10)) + } + form.Set("f03", strconv.FormatInt(int64(body.F03), 10)) + if body.F03Null != nil { + form.Set("f03Null", strconv.FormatInt(int64((*body.F03Null)), 10)) + } + form.Set("f04", strconv.FormatInt(body.F04, 10)) + if body.F04Null != nil { + form.Set("f04Null", strconv.FormatInt((*body.F04Null), 10)) + } + form.Set("f05", body.F05.Format(time.RFC3339)) + if body.F05Null != nil { + form.Set("f05Null", (*body.F05Null).Format(time.RFC3339)) + } + form.Set("f06", body.F06.String()) + if body.F06Null != nil { + form.Set("f06Null", (*body.F06Null).String()) + } + form.Set("f07", body.F07) + if body.F07Null != nil { + form.Set("f07Null", (*body.F07Null)) + } + form.Set("f08", body.F08.String()) + if body.F08Null != nil { + form.Set("f08Null", (*body.F08Null).String()) + } + form.Set("f09", body.F09.String()) + if body.F09Null != nil { + form.Set("f09Null", (*body.F09Null).String()) + } + for _, v := range body.F10 { + form.Add("f10", v) + } + for _, v := range body.F11 { + form.Add("f11", strconv.FormatInt(int64(v), 10)) + } + for _, v := range body.F12 { + form.Add("f12", v.String()) + } + form.Set("f13", body.F13) + if body.F13Null != nil { + form.Set("f13Null", (*body.F13Null)) + } + + reqBody := strings.NewReader(form.Encode()) + contentType := "application/x-www-form-urlencoded" + + req, err := http.NewRequestWithContext(ctx, "POST", u, reqBody) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", contentType) + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out FooBar + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// AddMultipartForm +func (c *Client) AddMultipartForm(ctx context.Context, body AddMultipartFormRequest) (*FooBar, error) { + u := c.baseURL + "/examples/form:multipart" + + var bodyBuf bytes.Buffer + + mw := multipart.NewWriter(&bodyBuf) + if err := mw.WriteField("f1", strconv.FormatBool(body.F1)); err != nil { + return nil, fmt.Errorf("multipart field f1: %w", err) + } + if err := mw.WriteField("f2", strconv.FormatInt(int64(body.F2), 10)); err != nil { + return nil, fmt.Errorf("multipart field f2: %w", err) + } + if err := mw.WriteField("f3", strconv.FormatInt(int64(body.F3), 10)); err != nil { + return nil, fmt.Errorf("multipart field f3: %w", err) + } + if err := mw.WriteField("f4", strconv.FormatInt(body.F4, 10)); err != nil { + return nil, fmt.Errorf("multipart field f4: %w", err) + } + if err := mw.WriteField("f5", body.F5.Format(time.RFC3339)); err != nil { + return nil, fmt.Errorf("multipart field f5: %w", err) + } + if err := mw.WriteField("f6", body.F6.String()); err != nil { + return nil, fmt.Errorf("multipart field f6: %w", err) + } + if err := mw.WriteField("f7", body.F7); err != nil { + return nil, fmt.Errorf("multipart field f7: %w", err) + } + { + part, err := mw.CreateFormFile("file1", body.File1.Filename) + if err != nil { + return nil, fmt.Errorf("multipart file file1: %w", err) + } + + if _, err := io.Copy(part, body.File1.File); err != nil { + return nil, fmt.Errorf("multipart file file1: %w", err) + } + } + if body.File2.File != nil { + { + part, err := mw.CreateFormFile("file2", body.File2.Filename) + if err != nil { + return nil, fmt.Errorf("multipart file file2: %w", err) + } + + if _, err := io.Copy(part, body.File2.File); err != nil { + return nil, fmt.Errorf("multipart file file2: %w", err) + } + } + } + + if err := mw.Close(); err != nil { + return nil, fmt.Errorf("multipart close: %w", err) + } + + reqBody := &bodyBuf + contentType := mw.FormDataContentType() + + req, err := http.NewRequestWithContext(ctx, "POST", u, reqBody) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", contentType) + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out FooBar + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// HeaderResponse +// Check header responses +func (c *Client) HeaderResponse(ctx context.Context) error { + u := c.baseURL + "/examples/header" + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return err + } + + resp, err := c.doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// AddInlinedAllOf +func (c *Client) AddInlinedAllOf(ctx context.Context, body AddInlinedAllOfRequest) (*FooBar, error) { + u := c.baseURL + "/examples/inlinedAllOf" + + buf, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("marshal request body: %w", err) + } + + reqBody := bytes.NewReader(buf) + contentType := "application/json" + + req, err := http.NewRequestWithContext(ctx, "POST", u, reqBody) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", contentType) + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out FooBar + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// AddInlinedBody +func (c *Client) AddInlinedBody(ctx context.Context, body AddInlinedBodyRequest) (*FooBar, error) { + u := c.baseURL + "/examples/inlinedBody" + + buf, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("marshal request body: %w", err) + } + + reqBody := bytes.NewReader(buf) + contentType := "application/json" + + req, err := http.NewRequestWithContext(ctx, "POST", u, reqBody) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", contentType) + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out FooBar + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// GetExampleParams +func (c *Client) GetExampleParams(ctx context.Context, k1 string, k2 uuid.UUID, k3 time.Time, k4 int32, k5 int64, enumTest GetExampleParamsEnumTest) (*Example, error) { + u := c.baseURL + "/examples/key1/{k1}/key2/{k2}/key3/{k3}/key4/{key4}/key5/{key5}" + queryParams := url.Values{} + u = strings.Replace(u, "{k1}", url.PathEscape(k1), 1) + u = strings.Replace(u, "{k2}", url.PathEscape(k2.String()), 1) + u = strings.Replace(u, "{k3}", url.PathEscape(k3.Format(time.RFC3339)), 1) + u = strings.Replace(u, "{k4}", url.PathEscape(strconv.FormatInt(int64(k4), 10)), 1) + u = strings.Replace(u, "{k5}", url.PathEscape(strconv.FormatInt(k5, 10)), 1) + queryParams.Set("enumTest", enumTest.String()) + + if len(queryParams) > 0 { + u += "?" + queryParams.Encode() + } + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out Example + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// NoResponse +func (c *Client) NoResponse(ctx context.Context, body Foo) error { + u := c.baseURL + "/examples/noResponse" + + buf, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("marshal request body: %w", err) + } + + reqBody := bytes.NewReader(buf) + contentType := "application/json" + + req, err := http.NewRequestWithContext(ctx, "POST", u, reqBody) + if err != nil { + return err + } + + req.Header.Set("Content-Type", contentType) + + resp, err := c.doer.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + return nil +} + +// GetExampleOptional +func (c *Client) GetExampleOptional(ctx context.Context, k1 *string, k2 *uuid.UUID, k3 *time.Time, k4 *int32, k5 *int64, k5Default int64) (*Example, error) { + u := c.baseURL + "/examples/optional" + queryParams := url.Values{} + if k1 != nil { + queryParams.Set("k1", (*k1)) + } + if k2 != nil { + queryParams.Set("k2", (*k2).String()) + } + if k3 != nil { + queryParams.Set("k3", (*k3).Format(time.RFC3339)) + } + if k4 != nil { + queryParams.Set("k4", strconv.FormatInt(int64((*k4)), 10)) + } + if k5 != nil { + queryParams.Set("k5", strconv.FormatInt((*k5), 10)) + } + queryParams.Set("k5Default", strconv.FormatInt(k5Default, 10)) + + if len(queryParams) > 0 { + u += "?" + queryParams.Encode() + } + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out Example + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// GetExampleQuery +func (c *Client) GetExampleQuery(ctx context.Context, k1 string, k2 uuid.UUID, k3 time.Time, k4 int32, k5 int64, k6 []string, k7 []uuid.UUID) (*Example, error) { + u := c.baseURL + "/examples/query" + queryParams := url.Values{} + queryParams.Set("k1", k1) + queryParams.Set("k2", k2.String()) + queryParams.Set("k3", k3.Format(time.RFC3339)) + queryParams.Set("k4", strconv.FormatInt(int64(k4), 10)) + queryParams.Set("k5", strconv.FormatInt(k5, 10)) + for _, v := range k6 { + queryParams.Add("k6", v) + } + for _, v := range k7 { + queryParams.Add("k7", v.String()) + } + + if len(queryParams) > 0 { + u += "?" + queryParams.Encode() + } + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out Example + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// GetRawBody +func (c *Client) GetRawBody(ctx context.Context, body Foo) (*Example, error) { + u := c.baseURL + "/examples/rawBody" + + buf, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("marshal request body: %w", err) + } + + reqBody := bytes.NewReader(buf) + contentType := "application/json" + + req, err := http.NewRequestWithContext(ctx, "GET", u, reqBody) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", contentType) + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out Example + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// GetRawRequest +func (c *Client) GetRawRequest(ctx context.Context, vehicle GetRawRequestVehicle) (*Example, error) { + u := c.baseURL + "/examples/rawRequest" + queryParams := url.Values{} + queryParams.Set("vehicle", vehicle.String()) + + if len(queryParams) > 0 { + u += "?" + queryParams.Encode() + } + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out Example + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// GetRawRequestResponse +func (c *Client) GetRawRequestResponse(ctx context.Context, vehicle GetRawRequestResponseVehicle) (*Example, error) { + u := c.baseURL + "/examples/rawRequestResponse" + queryParams := url.Values{} + queryParams.Set("vehicle", vehicle.String()) + + if len(queryParams) > 0 { + u += "?" + queryParams.Encode() + } + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out Example + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// GetRawRequestResponseAndHeaders +func (c *Client) GetRawRequestResponseAndHeaders(ctx context.Context, vehicle GetRawRequestResponseAndHeadersVehicle) (*Example, error) { + u := c.baseURL + "/examples/rawRequestResponseAndHeaders" + queryParams := url.Values{} + queryParams.Set("vehicle", vehicle.String()) + + if len(queryParams) > 0 { + u += "?" + queryParams.Encode() + } + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out Example + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// GetRawResponse +func (c *Client) GetRawResponse(ctx context.Context, vehicle GetRawResponseVehicle) (*Example, error) { + u := c.baseURL + "/examples/rawResponse" + queryParams := url.Values{} + queryParams.Set("vehicle", vehicle.String()) + + if len(queryParams) > 0 { + u += "?" + queryParams.Encode() + } + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out Example + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} + +// GetTest +func (c *Client) GetTest(ctx context.Context, vehicle GetTestVehicle, vehicleDefault GetTestVehicleDefault, playerID uuid.UUID, color ColorQuery, colorDefault ColorQueryDefault, season Season) (*Example, error) { + u := c.baseURL + "/examples/test" + queryParams := url.Values{} + queryParams.Set("vehicle", vehicle.String()) + queryParams.Set("vehicleDefault", vehicleDefault.String()) + queryParams.Set("playerId", playerID.String()) + queryParams.Set("color", color.String()) + queryParams.Set("colorDefault", colorDefault.String()) + u = strings.Replace(u, "{season}", url.PathEscape(season.String()), 1) + + if len(queryParams) > 0 { + u += "?" + queryParams.Encode() + } + + req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) + if err != nil { + return nil, err + } + + resp, err := c.doer.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + errBody, _ := io.ReadAll(resp.Body) + + return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + } + + var out Example + if err := httputil.GetJSONBody(resp.Body, &out); err != nil { + return nil, err + } + + return &out, nil +} diff --git a/tests/go.mod b/tests/go.mod index 2093f3d..dba2e32 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -6,6 +6,7 @@ require ( github.com/bir/iken v0.8.12 github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.11.1 + golang.org/x/oauth2 v0.36.0 ) require ( diff --git a/tests/go.sum b/tests/go.sum index 8ca2ccb..f564104 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -22,11 +22,11 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/tests/tests_main.go b/tests/tests_main.go index dfcdcfd..1797cb5 100644 --- a/tests/tests_main.go +++ b/tests/tests_main.go @@ -34,7 +34,7 @@ func main() { example.RegisterHTTP(ops, http.NewServeMux(), tokenAuth, tokenAuth, tokenAuth, tokenAuth, rawAuth, authorize) var authOps auth.Operations = &auth.Service{} - auth.RegisterHTTP(authOps, http.NewServeMux(), tokenAuth, tokenAuth, tokenAuth, basicAuth, tokenAuth, rawAuth, rawAuth, rawAuth, authorize) + auth.RegisterHTTP(authOps, http.NewServeMux(), tokenAuth, tokenAuth, tokenAuth, basicAuth, tokenAuth, rawAuth, rawAuth, rawAuth, rawAuth, authorize) os.Exit(0) } From 0780947cce656c9b50b9e0fdb135dfcfcb6991e1 Mon Sep 17 00:00:00 2001 From: David Newgas Date: Thu, 9 Jul 2026 15:24:22 -0700 Subject: [PATCH 2/9] Reduce amount of changes to openapi.go --- foji/openapi/client.go.tpl | 243 +++++++++++++++---------------- output/openapi.go | 83 +---------- tests/auth/foji.yaml | 1 + tests/auth/http_client_gen.go | 28 ---- tests/example/http_client_gen.go | 15 -- 5 files changed, 116 insertions(+), 254 deletions(-) diff --git a/foji/openapi/client.go.tpl b/foji/openapi/client.go.tpl index b525502..48fe50d 100644 --- a/foji/openapi/client.go.tpl +++ b/foji/openapi/client.go.tpl @@ -16,23 +16,112 @@ {{- end -}} {{- end -}} -{{- define "authProvided" -}} +{{- define "authScheme" -}} {{- $scheme := .RuntimeParams.scheme -}} - {{- $kind := $.SecuritySchemeKind $scheme -}} - {{- if eq $kind "basic" -}}{{ camel $scheme }}Username != "" - {{- else if eq $kind "authCode" -}}{{ camel $scheme }}Token != nil - {{- else if eq $kind "clientCredentials" -}}c.{{ camel $scheme }}Config != nil - {{- else -}}{{ camel $scheme }}Token != "" - {{- end -}} -{{- end -}} - -{{- define "authParams" -}} - {{- $scheme := .RuntimeParams.scheme -}} - {{- $kind := $.SecuritySchemeKind $scheme -}} - {{- if eq $kind "basic" }} {{ camel $scheme }}Username string, {{ camel $scheme }}Password string, - {{- else if eq $kind "authCode" }} {{ camel $scheme }}Token *oauth2.Token, - {{- else if eq $kind "clientCredentials" }} - {{- else }} {{ camel $scheme }}Token string, + {{- $mode := .RuntimeParams.mode -}} + {{- $s := index $.API.Components.SecuritySchemes $scheme -}} + {{- $c := camel $scheme -}} + {{- $kind := "apiKey" -}} + {{- if eq $s.Value.Type "http" -}} + {{- if eq $s.Value.Scheme "basic" -}}{{ $kind = "basic" }}{{- else -}}{{ $kind = "bearer" }}{{- end -}} + {{- else if eq $s.Value.Type "oauth2" -}} + {{- if and (isNotNil $s.Value.Flows) (isNotNil $s.Value.Flows.ClientCredentials) -}}{{ $kind = "clientCredentials" }}{{- else -}}{{ $kind = "authCode" }}{{- end -}} + {{- else if eq $s.Value.Type "openIdConnect" -}}{{ $kind = "authCode" }}{{- end -}} + + {{- if eq $mode "params" -}} + {{- if eq $kind "basic" }} {{ $c }}Username string, {{ $c }}Password string, + {{- else if eq $kind "authCode" }} {{ $c }}Token *oauth2.Token, + {{- else if eq $kind "clientCredentials" }} + {{- else }} {{ $c }}Token string, + {{- end -}} + {{- else if eq $mode "provided" -}} + {{- if eq $kind "basic" -}}{{ $c }}Username != "" + {{- else if eq $kind "authCode" -}}{{ $c }}Token != nil + {{- else if eq $kind "clientCredentials" -}}c.{{ $c }}Config != nil + {{- else -}}{{ $c }}Token != "" + {{- end -}} + {{- else if eq $mode "field" -}} + {{- if eq $kind "basic" }} + {{ $c }}Username string + {{ $c }}Password string + {{- else if eq $kind "authCode" }} + {{ $c }}Config oauth2.Config + {{- else if eq $kind "clientCredentials" }} + {{ $c }}Config *clientcredentials.Config + {{- else }} + {{ $c }}Token string + {{- end }} + {{- else if eq $mode "option" -}} + {{- if eq $kind "basic" }} +func With{{ pascal $scheme }}Credentials(username, password string) ClientOption { + return func(c *Client) { + c.{{ $c }}Username = username + c.{{ $c }}Password = password + } +} + {{- else if eq $kind "authCode" }} +func With{{ pascal $scheme }}Config(config oauth2.Config) ClientOption { + return func(c *Client) { + c.{{ $c }}Config = config + } +} + {{- else if eq $kind "clientCredentials" }} +func With{{ pascal $scheme }}Config(config clientcredentials.Config) ClientOption { + return func(c *Client) { + c.{{ $c }}Config = &config + } +} + {{- else }} +func With{{ pascal $scheme }}Token(token string) ClientOption { + return func(c *Client) { + c.{{ $c }}Token = token + } +} + {{- end }} + {{- else if eq $mode "resolve" -}} + {{- if eq $kind "basic" }} + if {{ $c }}Username == "" { + {{ $c }}Username = c.{{ $c }}Username + {{ $c }}Password = c.{{ $c }}Password + } + {{- else if or (eq $kind "authCode") (eq $kind "clientCredentials") }} + {{- else }} + if {{ $c }}Token == "" { + {{ $c }}Token = c.{{ $c }}Token + } + {{- end }} + {{- else if eq $mode "inject" -}} + {{- if eq $kind "bearer" }} + if {{ $c }}Token != "" { + req.Header.Set("Authorization", "Bearer "+{{ $c }}Token) + } + {{- else if eq $kind "basic" }} + if {{ $c }}Username != "" { + req.SetBasicAuth({{ $c }}Username, {{ $c }}Password) + } + {{- else if eq $kind "apiKey" }} + if {{ $c }}Token != "" { + {{- if eq $s.Value.In "header" }} + req.Header.Set("{{ $s.Value.Name }}", {{ $c }}Token) + {{- else if eq $s.Value.In "query" }} + q := req.URL.Query() + q.Set("{{ $s.Value.Name }}", {{ $c }}Token) + req.URL.RawQuery = q.Encode() + {{- else if eq $s.Value.In "cookie" }} + req.AddCookie(&http.Cookie{Name: "{{ $s.Value.Name }}", Value: {{ $c }}Token}) + {{- end }} + } + {{- end }} + {{- else if eq $mode "client" -}} + {{- if eq $kind "authCode" }} + if {{ $c }}Token != nil { + doer = c.{{ $c }}Config.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer), {{ $c }}Token) + } + {{- else if eq $kind "clientCredentials" }} + if c.{{ $c }}Config != nil { + doer = c.{{ $c }}Config.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer)) + } + {{- end }} {{- end -}} {{- end -}} @@ -41,7 +130,7 @@ {{- $op := .RuntimeParams.op -}} {{- $package := .RuntimeParams.package -}} {{- $body := .GetRequestBody $op -}} - {{- range $scheme := $.OpSecuritySchemes $op }}{{ template "authParams" ($.WithParams "scheme" $scheme) }}{{- end }} + {{- range $scheme := $.OpSecuritySchemes $op }}{{ template "authScheme" ($.WithParams "scheme" $scheme "mode" "params") }}{{- end }} {{- range $param := $.OpParams $path $op -}} {{- $name := print $op.OperationID " " $param.Value.Name -}} {{- if notEmpty $param.Ref }}{{ $name = trimPrefix "#/components/parameters/" $param.Ref }}{{ end -}} @@ -71,17 +160,11 @@ import ( "io" "mime/multipart" "net/http" - "net/url" "strconv" "strings" - "time" -{{- if .HasOAuth2 }} "golang.org/x/oauth2" -{{- end }} -{{- if .HasClientCredentials }} "golang.org/x/oauth2/clientcredentials" -{{- end }} {{- .CheckAllTypes $package ($.Params.GetWithDefault "Auth" "") -}} {{- range .GoImports }} "{{ . }}" @@ -97,19 +180,7 @@ type ClientOption func(*Client) type Client struct { baseURL string doer Doer -{{- range $security, $value := .API.Components.SecuritySchemes }} - {{- $kind := $.SecuritySchemeKind $security }} - {{- if eq $kind "basic" }} - {{ camel $security }}Username string - {{ camel $security }}Password string - {{- else if eq $kind "authCode" }} - {{ camel $security }}Config oauth2.Config - {{- else if eq $kind "clientCredentials" }} - {{ camel $security }}Config *clientcredentials.Config - {{- else }} - {{ camel $security }}Token string - {{- end }} -{{- end }} +{{- range $security, $value := .API.Components.SecuritySchemes }}{{ template "authScheme" ($.WithParams "scheme" $security "mode" "field") }}{{- end }} } func NewClient(baseURL string, doer Doer, opts ...ClientOption) *Client { @@ -123,37 +194,7 @@ func NewClient(baseURL string, doer Doer, opts ...ClientOption) *Client { } {{- range $security, $value := .API.Components.SecuritySchemes }} - {{- $kind := $.SecuritySchemeKind $security }} - {{- if eq $kind "basic" }} - -func With{{ pascal $security }}Credentials(username, password string) ClientOption { - return func(c *Client) { - c.{{ camel $security }}Username = username - c.{{ camel $security }}Password = password - } -} - {{- else if eq $kind "authCode" }} - -func With{{ pascal $security }}Config(config oauth2.Config) ClientOption { - return func(c *Client) { - c.{{ camel $security }}Config = config - } -} - {{- else if eq $kind "clientCredentials" }} - -func With{{ pascal $security }}Config(config clientcredentials.Config) ClientOption { - return func(c *Client) { - c.{{ camel $security }}Config = &config - } -} - {{- else }} - -func With{{ pascal $security }}Token(token string) ClientOption { - return func(c *Client) { - c.{{ camel $security }}Token = token - } -} - {{- end }} +{{ template "authScheme" ($.WithParams "scheme" $security "mode" "option") }} {{- end }} {{- if .HasAuthentication }} @@ -183,30 +224,14 @@ func (e *APIError) Error() string { {{- goDoc $op.Description }} func (c *Client) {{ pascal $op.OperationID }}(ctx context.Context, {{- template "clientMethodSignature" ($.WithParams "op" $op "package" $package "path" $path) }} { - {{- range $scheme := $.OpSecuritySchemes $op }} - {{- $kind := $.SecuritySchemeKind $scheme }} - {{- if eq $kind "basic" }} - - if {{ camel $scheme }}Username == "" { - {{ camel $scheme }}Username = c.{{ camel $scheme }}Username - {{ camel $scheme }}Password = c.{{ camel $scheme }}Password - } - {{- else if eq $kind "authCode" }} - {{- else if eq $kind "clientCredentials" }} - {{- else }} - - if {{ camel $scheme }}Token == "" { - {{ camel $scheme }}Token = c.{{ camel $scheme }}Token - } - {{- end }} - {{- end }} + {{- range $scheme := $.OpSecuritySchemes $op }}{{ template "authScheme" ($.WithParams "scheme" $scheme "mode" "resolve") }}{{- end }} {{- if $.HasAnyAuth $op }} {{- $groups := $.OpSecurityGroups $op }} {{- $optional := false }} {{- range $g := $groups }}{{ if eq (len $g) 0 }}{{ $optional = true }}{{ end }}{{ end }} {{- if not $optional }} - if !({{ range $i, $g := $groups }}{{ if $i }} || {{ end }}({{ range $j, $scheme := $g }}{{ if $j }} && {{ end }}{{ template "authProvided" ($.WithParams "scheme" $scheme) }}{{ end }}){{ end }}) { + if !({{ range $i, $g := $groups }}{{ if $i }} || {{ end }}({{ range $j, $scheme := $g }}{{ if $j }} && {{ end }}{{ template "authScheme" ($.WithParams "scheme" $scheme "mode" "provided") }}{{ end }}){{ end }}) { return {{ $errRet }}ErrMissingAuthToken } {{- end }} @@ -381,53 +406,13 @@ func (c *Client) {{ pascal $op.OperationID }}(ctx context.Context, {{- end }} {{- end }} - {{- $opHasOAuth := false }} - {{- range $scheme := $.OpSecuritySchemes $op }}{{ $k := $.SecuritySchemeKind $scheme }}{{ if or (eq $k "authCode") (eq $k "clientCredentials") }}{{ $opHasOAuth = true }}{{ end }}{{ end }} - {{- range $scheme := $.OpSecuritySchemes $op }} - {{- $s := index $.API.Components.SecuritySchemes $scheme }} - {{- $kind := $.SecuritySchemeKind $scheme }} - {{- if eq $kind "bearer" }} - - if {{ camel $scheme }}Token != "" { - req.Header.Set("Authorization", "Bearer "+{{ camel $scheme }}Token) - } - {{- else if eq $kind "basic" }} - - if {{ camel $scheme }}Username != "" { - req.SetBasicAuth({{ camel $scheme }}Username, {{ camel $scheme }}Password) - } - {{- else if eq $kind "apiKey" }} - - if {{ camel $scheme }}Token != "" { - {{- if eq $s.Value.In "header" }} - req.Header.Set("{{ $s.Value.Name }}", {{ camel $scheme }}Token) - {{- else if eq $s.Value.In "query" }} - q := req.URL.Query() - q.Set("{{ $s.Value.Name }}", {{ camel $scheme }}Token) - req.URL.RawQuery = q.Encode() - {{- else if eq $s.Value.In "cookie" }} - req.AddCookie(&http.Cookie{Name: "{{ $s.Value.Name }}", Value: {{ camel $scheme }}Token}) - {{- end }} - } - {{- end }} - {{- end }} + {{- range $scheme := $.OpSecuritySchemes $op }}{{ template "authScheme" ($.WithParams "scheme" $scheme "mode" "inject") }}{{- end }} + {{- $opHasOAuth := false }} + {{- range $scheme := $.OpSecuritySchemes $op }}{{ $s := index $.API.Components.SecuritySchemes $scheme }}{{ if or (eq $s.Value.Type "oauth2") (eq $s.Value.Type "openIdConnect") }}{{ $opHasOAuth = true }}{{ end }}{{ end }} {{- if $opHasOAuth }} doer := c.doer - {{- range $scheme := $.OpSecuritySchemes $op }} - {{- $kind := $.SecuritySchemeKind $scheme }} - {{- if eq $kind "authCode" }} - - if {{ camel $scheme }}Token != nil { - doer = c.{{ camel $scheme }}Config.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer), {{ camel $scheme }}Token) - } - {{- else if eq $kind "clientCredentials" }} - - if c.{{ camel $scheme }}Config != nil { - doer = c.{{ camel $scheme }}Config.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer)) - } - {{- end }} - {{- end }} + {{- range $scheme := $.OpSecuritySchemes $op }}{{ template "authScheme" ($.WithParams "scheme" $scheme "mode" "client") }}{{- end }} resp, err := doer.Do(req) {{- else }} diff --git a/output/openapi.go b/output/openapi.go index 2ae5c45..3bc083a 100644 --- a/output/openapi.go +++ b/output/openapi.go @@ -677,19 +677,6 @@ func (o *OpenAPIFileContext) OpSecuritySchemes(op *openapi3.Operation) []string return out } -// SecurityGroupSchemes returns the scheme names of a single security requirement group (the -// schemes that must ALL be satisfied together), sorted for deterministic output. -func (o *OpenAPIFileContext) SecurityGroupSchemes(group openapi3.SecurityRequirement) []string { - out := make([]string, 0, len(group)) - for k := range group { - out = append(out, k) - } - - slices.Sort(out) - - return out -} - // OpSecurityGroups returns the operation's security requirement groups as sorted scheme-name // slices (AND within a group, OR across groups), with duplicate groups removed so the generated // validation does not repeat identical conditions. An empty slice element represents an empty @@ -700,7 +687,7 @@ func (o *OpenAPIFileContext) OpSecurityGroups(op *openapi3.Operation) [][]string seen := map[string]bool{} for _, group := range o.OpSecurity(op) { - schemes := o.SecurityGroupSchemes(group) + schemes := mapKeysSorted(group) key := strings.Join(schemes, "\x00") if seen[key] { @@ -715,74 +702,6 @@ func (o *OpenAPIFileContext) OpSecurityGroups(op *openapi3.Operation) [][]string return out } -// SecuritySchemeKind classifies a security scheme for client generation. It returns one of: -// - "basic", "bearer", "apiKey" (the default, which also covers custom x-raw-auth schemes); -// - "clientCredentials" for an OAuth2 scheme that declares a client-credentials flow, which the -// client drives entirely from a clientcredentials.Config (no per-request token); -// - "authCode" for any other OAuth2 flow or an OpenID Connect scheme, which the client drives -// from an oauth2.Config plus a per-request *oauth2.Token. -func (o *OpenAPIFileContext) SecuritySchemeKind(name string) string { - if o.API.Components == nil { - return "" - } - - s := o.API.Components.SecuritySchemes[name] - if s == nil || s.Value == nil { - return "" - } - - switch s.Value.Type { - case "http": - if s.Value.Scheme == "basic" { - return "basic" - } - - return "bearer" - case "oauth2": - if s.Value.Flows != nil && s.Value.Flows.ClientCredentials != nil { - return "clientCredentials" - } - - return "authCode" - case "openIdConnect": - return "authCode" - default: - return "apiKey" - } -} - -// HasOAuth2 reports whether any security scheme uses OAuth2 or OpenID Connect. Both drive the -// request through an oauth2-wrapped HTTP client, so the "golang.org/x/oauth2" package is needed. -func (o *OpenAPIFileContext) HasOAuth2() bool { - if o.API.Components == nil { - return false - } - - for _, s := range o.API.Components.SecuritySchemes { - if s != nil && s.Value != nil && (s.Value.Type == "oauth2" || s.Value.Type == "openIdConnect") { - return true - } - } - - return false -} - -// HasClientCredentials reports whether any security scheme is an OAuth2 client-credentials flow, -// which requires the "golang.org/x/oauth2/clientcredentials" package. -func (o *OpenAPIFileContext) HasClientCredentials() bool { - if o.API.Components == nil { - return false - } - - for name := range o.API.Components.SecuritySchemes { - if o.SecuritySchemeKind(name) == "clientCredentials" { - return true - } - } - - return false -} - func hasAuthorization(security openapi3.SecurityRequirements) bool { for _, ss := range security { for _, scopes := range ss { diff --git a/tests/auth/foji.yaml b/tests/auth/foji.yaml index b1394c9..c2591dd 100644 --- a/tests/auth/foji.yaml +++ b/tests/auth/foji.yaml @@ -7,6 +7,7 @@ processes: params: Package: foji/tests/auth Auth: tests/example.ExampleAuth + ClientAuth: tests/example.ExampleAuth OpenAPIFile: 'tests/auth/http_handler_gen.go': foji/openapi/handler.go.tpl 'tests/auth/http_client_gen.go': foji/openapi/client.go.tpl diff --git a/tests/auth/http_client_gen.go b/tests/auth/http_client_gen.go index 7371b9d..2b550ba 100644 --- a/tests/auth/http_client_gen.go +++ b/tests/auth/http_client_gen.go @@ -120,7 +120,6 @@ func (c *Client) ListAdminUsers(ctx context.Context, apiKeyHeaderToken string, b if apiKeyHeaderToken == "" { apiKeyHeaderToken = c.apiKeyHeaderToken } - if bearerAuthToken == "" { bearerAuthToken = c.bearerAuthToken } @@ -135,11 +134,9 @@ func (c *Client) ListAdminUsers(ctx context.Context, apiKeyHeaderToken string, b if err != nil { return nil, err } - if apiKeyHeaderToken != "" { req.Header.Set("X-API-Key", apiKeyHeaderToken) } - if bearerAuthToken != "" { req.Header.Set("Authorization", "Bearer "+bearerAuthToken) } @@ -171,15 +168,12 @@ func (c *Client) QueryDataWithApiKey(ctx context.Context, apiKeyCookieToken stri if apiKeyCookieToken == "" { apiKeyCookieToken = c.apiKeyCookieToken } - if apiKeyHeaderToken == "" { apiKeyHeaderToken = c.apiKeyHeaderToken } - if apiKeyQueryToken == "" { apiKeyQueryToken = c.apiKeyQueryToken } - if rawToken == "" { rawToken = c.rawToken } @@ -202,21 +196,17 @@ func (c *Client) QueryDataWithApiKey(ctx context.Context, apiKeyCookieToken stri if err != nil { return err } - if apiKeyCookieToken != "" { req.AddCookie(&http.Cookie{Name: "cookie_name", Value: apiKeyCookieToken}) } - if apiKeyHeaderToken != "" { req.Header.Set("X-API-Key", apiKeyHeaderToken) } - if apiKeyQueryToken != "" { q := req.URL.Query() q.Set("query_key_name", apiKeyQueryToken) req.URL.RawQuery = q.Encode() } - if rawToken != "" { req.Header.Set("Authorization", rawToken) } @@ -255,7 +245,6 @@ func (c *Client) ListDocuments(ctx context.Context, basicAuthUsername string, ba if err != nil { return err } - if basicAuthUsername != "" { req.SetBasicAuth(basicAuthUsername, basicAuthPassword) } @@ -282,7 +271,6 @@ func (c *Client) CreateDocument(ctx context.Context, apiKeyHeaderToken string, b if apiKeyHeaderToken == "" { apiKeyHeaderToken = c.apiKeyHeaderToken } - if bearerAuthToken == "" { bearerAuthToken = c.bearerAuthToken } @@ -297,11 +285,9 @@ func (c *Client) CreateDocument(ctx context.Context, apiKeyHeaderToken string, b if err != nil { return err } - if apiKeyHeaderToken != "" { req.Header.Set("X-API-Key", apiKeyHeaderToken) } - if bearerAuthToken != "" { req.Header.Set("Authorization", "Bearer "+bearerAuthToken) } @@ -336,7 +322,6 @@ func (c *Client) Overview(ctx context.Context) error { return err } doer := c.doer - if c.oauth2ClientCredentialsExampleConfig != nil { doer = c.oauth2ClientCredentialsExampleConfig.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer)) } @@ -371,7 +356,6 @@ func (c *Client) GetDetailedProfile(ctx context.Context, openIdconnectToken *oau return err } doer := c.doer - if openIdconnectToken != nil { doer = c.openIdconnectConfig.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer), openIdconnectToken) } @@ -402,16 +386,13 @@ func (c *Client) GetProtectedResource(ctx context.Context, apiKeyCookieToken str if apiKeyCookieToken == "" { apiKeyCookieToken = c.apiKeyCookieToken } - if apiKeyHeaderToken == "" { apiKeyHeaderToken = c.apiKeyHeaderToken } - if basicAuthUsername == "" { basicAuthUsername = c.basicAuthUsername basicAuthPassword = c.basicAuthPassword } - if bearerAuthToken == "" { bearerAuthToken = c.bearerAuthToken } @@ -426,19 +407,15 @@ func (c *Client) GetProtectedResource(ctx context.Context, apiKeyCookieToken str if err != nil { return err } - if apiKeyCookieToken != "" { req.AddCookie(&http.Cookie{Name: "cookie_name", Value: apiKeyCookieToken}) } - if apiKeyHeaderToken != "" { req.Header.Set("X-API-Key", apiKeyHeaderToken) } - if basicAuthUsername != "" { req.SetBasicAuth(basicAuthUsername, basicAuthPassword) } - if bearerAuthToken != "" { req.Header.Set("Authorization", "Bearer "+bearerAuthToken) } @@ -496,7 +473,6 @@ func (c *Client) GetCurrentUser(ctx context.Context, apiKeyHeaderToken string, b if apiKeyHeaderToken == "" { apiKeyHeaderToken = c.apiKeyHeaderToken } - if bearerAuthToken == "" { bearerAuthToken = c.bearerAuthToken } @@ -511,16 +487,13 @@ func (c *Client) GetCurrentUser(ctx context.Context, apiKeyHeaderToken string, b if err != nil { return nil, err } - if apiKeyHeaderToken != "" { req.Header.Set("X-API-Key", apiKeyHeaderToken) } - if bearerAuthToken != "" { req.Header.Set("Authorization", "Bearer "+bearerAuthToken) } doer := c.doer - if oauth2ExampleToken != nil { doer = c.oauth2ExampleConfig.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer), oauth2ExampleToken) } @@ -563,7 +536,6 @@ func (c *Client) GetUserProfile(ctx context.Context, bearerAuthToken string) (*U if err != nil { return nil, err } - if bearerAuthToken != "" { req.Header.Set("Authorization", "Bearer "+bearerAuthToken) } diff --git a/tests/example/http_client_gen.go b/tests/example/http_client_gen.go index 3b9fa30..d92f9d9 100644 --- a/tests/example/http_client_gen.go +++ b/tests/example/http_client_gen.go @@ -122,7 +122,6 @@ func (c *Client) GetAuthComplex(ctx context.Context, headerAuthToken string, jwt if headerAuthToken == "" { headerAuthToken = c.headerAuthToken } - if jwtToken == "" { jwtToken = c.jwtToken } @@ -137,11 +136,9 @@ func (c *Client) GetAuthComplex(ctx context.Context, headerAuthToken string, jwt if err != nil { return err } - if headerAuthToken != "" { req.Header.Set("Authorization", headerAuthToken) } - if jwtToken != "" { q := req.URL.Query() q.Set("jwt", jwtToken) @@ -179,7 +176,6 @@ func (c *Client) GetAuthSimple(ctx context.Context, headerAuthToken string) erro if err != nil { return err } - if headerAuthToken != "" { req.Header.Set("Authorization", headerAuthToken) } @@ -211,7 +207,6 @@ func (c *Client) GetAuthSimpleMaybe(ctx context.Context, headerAuthToken string) if err != nil { return err } - if headerAuthToken != "" { req.Header.Set("Authorization", headerAuthToken) } @@ -247,7 +242,6 @@ func (c *Client) GetAuthSimple2(ctx context.Context, headerAuthToken string) err if err != nil { return err } - if headerAuthToken != "" { req.Header.Set("Authorization", headerAuthToken) } @@ -279,7 +273,6 @@ func (c *Client) GetAuthSimple2Maybe(ctx context.Context, headerAuthToken string if err != nil { return err } - if headerAuthToken != "" { req.Header.Set("Authorization", headerAuthToken) } @@ -304,7 +297,6 @@ func (c *Client) GetAuthComplexMaybe(ctx context.Context, headerAuthToken string if headerAuthToken == "" { headerAuthToken = c.headerAuthToken } - if jwtToken == "" { jwtToken = c.jwtToken } @@ -315,11 +307,9 @@ func (c *Client) GetAuthComplexMaybe(ctx context.Context, headerAuthToken string if err != nil { return err } - if headerAuthToken != "" { req.Header.Set("Authorization", headerAuthToken) } - if jwtToken != "" { q := req.URL.Query() q.Set("jwt", jwtToken) @@ -346,11 +336,9 @@ func (c *Client) GetComplexSecurity(ctx context.Context, bearerToken string, cus if bearerToken == "" { bearerToken = c.bearerToken } - if customHeaderAuthToken == "" { customHeaderAuthToken = c.customHeaderAuthToken } - if rawToken == "" { rawToken = c.rawToken } @@ -365,15 +353,12 @@ func (c *Client) GetComplexSecurity(ctx context.Context, bearerToken string, cus if err != nil { return nil, err } - if bearerToken != "" { req.Header.Set("Authorization", "Bearer "+bearerToken) } - if customHeaderAuthToken != "" { req.Header.Set("X-CUSTOM-HEADER", customHeaderAuthToken) } - if rawToken != "" { req.Header.Set("Authorization", rawToken) } From 42659d43f9705327fb1540768db1addce4a53ce2 Mon Sep 17 00:00:00 2001 From: David Newgas Date: Fri, 10 Jul 2026 09:29:43 -0700 Subject: [PATCH 3/9] Use iken error and auth types This commit changes the generated client library to use a (not yet merged) iken change that adds an standard error and several auth types akin to those used in handler.go.tpl. This has the effect of: 1) Allowing clients to use a single error.As that works across any client. 2) Allowing clients to pass a user object in the API methods, with helpers that translate the user object into the actual wire credential. This matches the handler.go.tpl style. See https://gist.github.com/davidn/8b5ed70a4704e1dcb0f9c63159632ef8 for an example of how this gets used. --- foji/openapi/client.go.tpl | 228 +++++---------- tests/auth/http_client_gen.go | 398 ++++++++++----------------- tests/auth/openapi.yaml | 1 + tests/csvresponse/foji.yaml | 1 + tests/csvresponse/http_client_gen.go | 69 ++--- tests/example/foji.yaml | 1 + tests/example/http_client_gen.go | 352 +++++++++++------------ tests/go.mod | 1 - tests/go.sum | 4 - tests/tests_main.go | 29 ++ 10 files changed, 421 insertions(+), 663 deletions(-) diff --git a/foji/openapi/client.go.tpl b/foji/openapi/client.go.tpl index 48fe50d..25d2e31 100644 --- a/foji/openapi/client.go.tpl +++ b/foji/openapi/client.go.tpl @@ -16,112 +16,51 @@ {{- end -}} {{- end -}} -{{- define "authScheme" -}} +{{- /* clientAuth classifies a security scheme and emits the fragment for the requested mode: + param (NewClient argument), construct (Client field initializer) or apply (per-request call). */}} +{{- define "clientAuth" -}} {{- $scheme := .RuntimeParams.scheme -}} {{- $mode := .RuntimeParams.mode -}} {{- $s := index $.API.Components.SecuritySchemes $scheme -}} + {{- $v := $s.Value -}} {{- $c := camel $scheme -}} - {{- $kind := "apiKey" -}} - {{- if eq $s.Value.Type "http" -}} - {{- if eq $s.Value.Scheme "basic" -}}{{ $kind = "basic" }}{{- else -}}{{ $kind = "bearer" }}{{- end -}} - {{- else if eq $s.Value.Type "oauth2" -}} - {{- if and (isNotNil $s.Value.Flows) (isNotNil $s.Value.Flows.ClientCredentials) -}}{{ $kind = "clientCredentials" }}{{- else -}}{{ $kind = "authCode" }}{{- end -}} - {{- else if eq $s.Value.Type "openIdConnect" -}}{{ $kind = "authCode" }}{{- end -}} - - {{- if eq $mode "params" -}} - {{- if eq $kind "basic" }} {{ $c }}Username string, {{ $c }}Password string, - {{- else if eq $kind "authCode" }} {{ $c }}Token *oauth2.Token, - {{- else if eq $kind "clientCredentials" }} - {{- else }} {{ $c }}Token string, + {{- $kind := "header" -}} + {{- if $.SecurityHasExtension $s "x-raw-client-auth" -}}{{ $kind = "raw" }} + {{- else if eq $v.Type "http" -}} + {{- if eq $v.Scheme "basic" -}}{{ $kind = "basic" }}{{- else -}}{{ $kind = "bearer" }}{{- end -}} + {{- else if or (eq $v.Type "oauth2") (eq $v.Type "openIdConnect") -}}{{ $kind = "wrap" }} + {{- else if eq $v.In "query" -}}{{ $kind = "query" }} + {{- else if eq $v.In "cookie" -}}{{ $kind = "cookie" }} + {{- else -}}{{ $kind = "header" }}{{- end -}} + + {{- if eq $mode "param" -}} + {{- if eq $kind "raw" -}}{{ $c }}Auth ClientAuthenticator + {{- else if eq $kind "basic" -}}{{ $c }}Auth ClientBasicAuthenticator + {{- else if eq $kind "cookie" -}}{{ $c }}Auth ClientCookieAuthenticator + {{- else if eq $kind "wrap" -}}{{ $c }}Auth ClientWrappingAuthenticator + {{- else -}}{{ $c }}Auth ClientTokenAuthenticator {{- end -}} - {{- else if eq $mode "provided" -}} - {{- if eq $kind "basic" -}}{{ $c }}Username != "" - {{- else if eq $kind "authCode" -}}{{ $c }}Token != nil - {{- else if eq $kind "clientCredentials" -}}c.{{ $c }}Config != nil - {{- else -}}{{ $c }}Token != "" - {{- end -}} - {{- else if eq $mode "field" -}} - {{- if eq $kind "basic" }} - {{ $c }}Username string - {{ $c }}Password string - {{- else if eq $kind "authCode" }} - {{ $c }}Config oauth2.Config - {{- else if eq $kind "clientCredentials" }} - {{ $c }}Config *clientcredentials.Config - {{- else }} - {{ $c }}Token string - {{- end }} - {{- else if eq $mode "option" -}} - {{- if eq $kind "basic" }} -func With{{ pascal $scheme }}Credentials(username, password string) ClientOption { - return func(c *Client) { - c.{{ $c }}Username = username - c.{{ $c }}Password = password - } -} - {{- else if eq $kind "authCode" }} -func With{{ pascal $scheme }}Config(config oauth2.Config) ClientOption { - return func(c *Client) { - c.{{ $c }}Config = config - } -} - {{- else if eq $kind "clientCredentials" }} -func With{{ pascal $scheme }}Config(config clientcredentials.Config) ClientOption { - return func(c *Client) { - c.{{ $c }}Config = &config - } -} - {{- else }} -func With{{ pascal $scheme }}Token(token string) ClientOption { - return func(c *Client) { - c.{{ $c }}Token = token - } -} - {{- end }} - {{- else if eq $mode "resolve" -}} - {{- if eq $kind "basic" }} - if {{ $c }}Username == "" { - {{ $c }}Username = c.{{ $c }}Username - {{ $c }}Password = c.{{ $c }}Password - } - {{- else if or (eq $kind "authCode") (eq $kind "clientCredentials") }} - {{- else }} - if {{ $c }}Token == "" { - {{ $c }}Token = c.{{ $c }}Token - } - {{- end }} - {{- else if eq $mode "inject" -}} - {{- if eq $kind "bearer" }} - if {{ $c }}Token != "" { - req.Header.Set("Authorization", "Bearer "+{{ $c }}Token) - } + {{- else if eq $mode "construct" }} + {{- if eq $kind "raw" }} + {{ $c }}Auth: {{ $c }}Auth, + {{- else if eq $kind "header" }} + {{ $c }}Auth: httputil.HeaderClientAuth("{{ $v.Name }}", {{ $c }}Auth), + {{- else if eq $kind "query" }} + {{ $c }}Auth: httputil.QueryClientAuth("{{ $v.Name }}", {{ $c }}Auth), + {{- else if eq $kind "bearer" }} + {{ $c }}Auth: httputil.BearerClientAuth("Authorization", {{ $c }}Auth), {{- else if eq $kind "basic" }} - if {{ $c }}Username != "" { - req.SetBasicAuth({{ $c }}Username, {{ $c }}Password) - } - {{- else if eq $kind "apiKey" }} - if {{ $c }}Token != "" { - {{- if eq $s.Value.In "header" }} - req.Header.Set("{{ $s.Value.Name }}", {{ $c }}Token) - {{- else if eq $s.Value.In "query" }} - q := req.URL.Query() - q.Set("{{ $s.Value.Name }}", {{ $c }}Token) - req.URL.RawQuery = q.Encode() - {{- else if eq $s.Value.In "cookie" }} - req.AddCookie(&http.Cookie{Name: "{{ $s.Value.Name }}", Value: {{ $c }}Token}) - {{- end }} - } - {{- end }} - {{- else if eq $mode "client" -}} - {{- if eq $kind "authCode" }} - if {{ $c }}Token != nil { - doer = c.{{ $c }}Config.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer), {{ $c }}Token) - } - {{- else if eq $kind "clientCredentials" }} - if c.{{ $c }}Config != nil { - doer = c.{{ $c }}Config.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer)) + {{ $c }}Auth: httputil.BasicClientAuth({{ $c }}Auth), + {{- else if eq $kind "cookie" }} + {{ $c }}Auth: httputil.CookieClientAuth({{ $c }}Auth), + {{- else if eq $kind "wrap" }} + {{ $c }}Auth: httputil.WrapClientAuth({{ $c }}Auth), + {{- end -}} + {{- else if eq $mode "apply" }} + httpClient, err = c.{{ $c }}Auth(req, httpClient, user) + if err != nil { + return {{ $.RuntimeParams.errRet }}err } - {{- end }} {{- end -}} {{- end -}} @@ -130,7 +69,7 @@ func With{{ pascal $scheme }}Token(token string) ClientOption { {{- $op := .RuntimeParams.op -}} {{- $package := .RuntimeParams.package -}} {{- $body := .GetRequestBody $op -}} - {{- range $scheme := $.OpSecuritySchemes $op }}{{ template "authScheme" ($.WithParams "scheme" $scheme "mode" "params") }}{{- end }} + {{- if $.OpSecuritySchemes $op }} user *{{ $.CheckPackage ($.Params.GetWithDefault "ClientAuth" "") $package }},{{ end }} {{- range $param := $.OpParams $path $op -}} {{- $name := print $op.OperationID " " $param.Value.Name -}} {{- if notEmpty $param.Ref }}{{ $name = trimPrefix "#/components/parameters/" $param.Ref }}{{ end -}} @@ -146,6 +85,7 @@ func With{{ pascal $scheme }}Token(token string) ClientOption { {{- end -}} {{- $package := $.PackageName }} +{{- $clientAuth := $.Params.GetWithDefault "ClientAuth" "" }} // Code generated by foji {{ version }}, template: {{ templateFile }}; DO NOT EDIT. @@ -155,7 +95,6 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "io" "mime/multipart" @@ -163,52 +102,41 @@ import ( "strconv" "strings" - "golang.org/x/oauth2" - "golang.org/x/oauth2/clientcredentials" -{{- .CheckAllTypes $package ($.Params.GetWithDefault "Auth" "") -}} + "github.com/bir/iken/httputil" +{{- .CheckAllTypes $package $clientAuth -}} {{- range .GoImports }} "{{ . }}" {{- end }} ) -type Doer interface { - Do(*http.Request) (*http.Response, error) -} - -type ClientOption func(*Client) - -type Client struct { - baseURL string - doer Doer -{{- range $security, $value := .API.Components.SecuritySchemes }}{{ template "authScheme" ($.WithParams "scheme" $security "mode" "field") }}{{- end }} -} - -func NewClient(baseURL string, doer Doer, opts ...ClientOption) *Client { - c := &Client{baseURL: baseURL, doer: doer} +{{ if .HasAuthentication -}} +{{ .ErrorIf (empty $clientAuth) "params.ClientAuth" -}} - for _, opt := range opts { - opt(c) - } +type ( + ClientAuthenticator = httputil.ClientAuthenticateFunc[*{{ $.CheckPackage $clientAuth $package }}] + ClientTokenAuthenticator = httputil.ClientTokenAuthenticatorFunc[*{{ $.CheckPackage $clientAuth $package }}] + ClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*{{ $.CheckPackage $clientAuth $package }}] + ClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*{{ $.CheckPackage $clientAuth $package }}] + ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*{{ $.CheckPackage $clientAuth $package }}] +) - return c -} +{{ end -}} +type Client struct { + baseURL string + httpClient *http.Client {{- range $security, $value := .API.Components.SecuritySchemes }} -{{ template "authScheme" ($.WithParams "scheme" $security "mode" "option") }} + {{ camel $security }}Auth ClientAuthenticator {{- end }} - -{{- if .HasAuthentication }} -var ErrMissingAuthToken = errors.New("missing auth token") -{{- end }} - -type APIError struct { - StatusCode int - Status string - Body []byte } -func (e *APIError) Error() string { - return fmt.Sprintf("%d %s: %s", e.StatusCode, e.Status, string(e.Body)) +func NewClient(baseURL string, httpClient *http.Client +{{- range $security, $value := .API.Components.SecuritySchemes }}, {{ template "clientAuth" ($.WithParams "scheme" $security "mode" "param") }}{{- end }}) *Client { + return &Client{ + baseURL: baseURL, + httpClient: httpClient, +{{- range $security, $value := .API.Components.SecuritySchemes }}{{ template "clientAuth" ($.WithParams "scheme" $security "mode" "construct") }}{{- end }} + } } {{- range $name, $path := .API.Paths.Map }} @@ -224,19 +152,6 @@ func (e *APIError) Error() string { {{- goDoc $op.Description }} func (c *Client) {{ pascal $op.OperationID }}(ctx context.Context, {{- template "clientMethodSignature" ($.WithParams "op" $op "package" $package "path" $path) }} { - {{- range $scheme := $.OpSecuritySchemes $op }}{{ template "authScheme" ($.WithParams "scheme" $scheme "mode" "resolve") }}{{- end }} - {{- if $.HasAnyAuth $op }} - {{- $groups := $.OpSecurityGroups $op }} - {{- $optional := false }} - {{- range $g := $groups }}{{ if eq (len $g) 0 }}{{ $optional = true }}{{ end }}{{ end }} - {{- if not $optional }} - - if !({{ range $i, $g := $groups }}{{ if $i }} || {{ end }}({{ range $j, $scheme := $g }}{{ if $j }} && {{ end }}{{ template "authScheme" ($.WithParams "scheme" $scheme "mode" "provided") }}{{ end }}){{ end }}) { - return {{ $errRet }}ErrMissingAuthToken - } - {{- end }} - {{- end }} - u := c.baseURL + "{{ $name }}" {{- $hasQuery := false }} {{- range $param := $.OpParams $path $op }}{{ if eq $param.Value.In "query" }}{{ $hasQuery = true }}{{ end }}{{ end }} @@ -406,19 +321,10 @@ func (c *Client) {{ pascal $op.OperationID }}(ctx context.Context, {{- end }} {{- end }} - {{- range $scheme := $.OpSecuritySchemes $op }}{{ template "authScheme" ($.WithParams "scheme" $scheme "mode" "inject") }}{{- end }} - - {{- $opHasOAuth := false }} - {{- range $scheme := $.OpSecuritySchemes $op }}{{ $s := index $.API.Components.SecuritySchemes $scheme }}{{ if or (eq $s.Value.Type "oauth2") (eq $s.Value.Type "openIdConnect") }}{{ $opHasOAuth = true }}{{ end }}{{ end }} - {{- if $opHasOAuth }} - doer := c.doer - {{- range $scheme := $.OpSecuritySchemes $op }}{{ template "authScheme" ($.WithParams "scheme" $scheme "mode" "client") }}{{- end }} + httpClient := c.httpClient + {{- range $scheme := $.OpSecuritySchemes $op }}{{ template "clientAuth" ($.WithParams "scheme" $scheme "mode" "apply" "errRet" $errRet) }}{{- end }} - resp, err := doer.Do(req) - {{- else }} - - resp, err := c.doer.Do(req) - {{- end }} + resp, err := httpClient.Do(req) if err != nil { return {{ $errRet }}err } @@ -432,7 +338,7 @@ func (c *Client) {{ pascal $op.OperationID }}(ctx context.Context, {{- end }} errBody, _ := io.ReadAll(resp.Body) - return {{ $errRet }}&APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return {{ $errRet }}httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } {{- if eq $respType "" }} diff --git a/tests/auth/http_client_gen.go b/tests/auth/http_client_gen.go index 2b550ba..f204041 100644 --- a/tests/auth/http_client_gen.go +++ b/tests/auth/http_client_gen.go @@ -4,144 +4,74 @@ package auth import ( "context" - "errors" - "fmt" "io" "net/http" "net/url" "github.com/bir/iken/httputil" - "golang.org/x/oauth2" - "golang.org/x/oauth2/clientcredentials" + "tests/example" ) -type Doer interface { - Do(*http.Request) (*http.Response, error) -} - -type ClientOption func(*Client) +type ( + ClientAuthenticator = httputil.ClientAuthenticateFunc[*example.ExampleAuth] + ClientTokenAuthenticator = httputil.ClientTokenAuthenticatorFunc[*example.ExampleAuth] + ClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*example.ExampleAuth] + ClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*example.ExampleAuth] + ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*example.ExampleAuth] +) type Client struct { - baseURL string - doer Doer - apiKeyCookieToken string - apiKeyHeaderToken string - apiKeyQueryToken string - basicAuthUsername string - basicAuthPassword string - bearerAuthToken string - oauth2ClientCredentialsExampleConfig *clientcredentials.Config - oauth2ExampleConfig oauth2.Config - openIdconnectConfig oauth2.Config - rawToken string -} - -func NewClient(baseURL string, doer Doer, opts ...ClientOption) *Client { - c := &Client{baseURL: baseURL, doer: doer} - - for _, opt := range opts { - opt(c) - } - - return c -} - -func WithApiKeyCookieToken(token string) ClientOption { - return func(c *Client) { - c.apiKeyCookieToken = token - } -} - -func WithApiKeyHeaderToken(token string) ClientOption { - return func(c *Client) { - c.apiKeyHeaderToken = token - } -} - -func WithApiKeyQueryToken(token string) ClientOption { - return func(c *Client) { - c.apiKeyQueryToken = token - } -} - -func WithBasicAuthCredentials(username, password string) ClientOption { - return func(c *Client) { - c.basicAuthUsername = username - c.basicAuthPassword = password - } -} - -func WithBearerAuthToken(token string) ClientOption { - return func(c *Client) { - c.bearerAuthToken = token - } -} - -func WithOauth2ClientCredentialsExampleConfig(config clientcredentials.Config) ClientOption { - return func(c *Client) { - c.oauth2ClientCredentialsExampleConfig = &config - } -} - -func WithOauth2ExampleConfig(config oauth2.Config) ClientOption { - return func(c *Client) { - c.oauth2ExampleConfig = config - } -} - -func WithOpenIdconnectConfig(config oauth2.Config) ClientOption { - return func(c *Client) { - c.openIdconnectConfig = config - } + baseURL string + httpClient *http.Client + apiKeyCookieAuth ClientAuthenticator + apiKeyHeaderAuth ClientAuthenticator + apiKeyQueryAuth ClientAuthenticator + basicAuthAuth ClientAuthenticator + bearerAuthAuth ClientAuthenticator + oauth2ClientCredentialsExampleAuth ClientAuthenticator + oauth2ExampleAuth ClientAuthenticator + openIdconnectAuth ClientAuthenticator + rawAuth ClientAuthenticator } -func WithRawToken(token string) ClientOption { - return func(c *Client) { - c.rawToken = token +func NewClient(baseURL string, httpClient *http.Client, apiKeyCookieAuth ClientCookieAuthenticator, apiKeyHeaderAuth ClientTokenAuthenticator, apiKeyQueryAuth ClientTokenAuthenticator, basicAuthAuth ClientBasicAuthenticator, bearerAuthAuth ClientTokenAuthenticator, oauth2ClientCredentialsExampleAuth ClientWrappingAuthenticator, oauth2ExampleAuth ClientWrappingAuthenticator, openIdconnectAuth ClientWrappingAuthenticator, rawAuth ClientAuthenticator) *Client { + return &Client{ + baseURL: baseURL, + httpClient: httpClient, + apiKeyCookieAuth: httputil.CookieClientAuth(apiKeyCookieAuth), + apiKeyHeaderAuth: httputil.HeaderClientAuth("X-API-Key", apiKeyHeaderAuth), + apiKeyQueryAuth: httputil.QueryClientAuth("query_key_name", apiKeyQueryAuth), + basicAuthAuth: httputil.BasicClientAuth(basicAuthAuth), + bearerAuthAuth: httputil.BearerClientAuth("Authorization", bearerAuthAuth), + oauth2ClientCredentialsExampleAuth: httputil.WrapClientAuth(oauth2ClientCredentialsExampleAuth), + oauth2ExampleAuth: httputil.WrapClientAuth(oauth2ExampleAuth), + openIdconnectAuth: httputil.WrapClientAuth(openIdconnectAuth), + rawAuth: rawAuth, } } -var ErrMissingAuthToken = errors.New("missing auth token") - -type APIError struct { - StatusCode int - Status string - Body []byte -} - -func (e *APIError) Error() string { - return fmt.Sprintf("%d %s: %s", e.StatusCode, e.Status, string(e.Body)) -} - // ListAdminUsers // List all users (admin only) // Requires both API key AND bearer token with admin scope -func (c *Client) ListAdminUsers(ctx context.Context, apiKeyHeaderToken string, bearerAuthToken string) ([]User, error) { - if apiKeyHeaderToken == "" { - apiKeyHeaderToken = c.apiKeyHeaderToken - } - if bearerAuthToken == "" { - bearerAuthToken = c.bearerAuthToken - } - - if !(apiKeyHeaderToken != "" && bearerAuthToken != "") { - return nil, ErrMissingAuthToken - } - +func (c *Client) ListAdminUsers(ctx context.Context, user *example.ExampleAuth) ([]User, error) { u := c.baseURL + "/admin/users" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return nil, err } - if apiKeyHeaderToken != "" { - req.Header.Set("X-API-Key", apiKeyHeaderToken) + + httpClient := c.httpClient + httpClient, err = c.apiKeyHeaderAuth(req, httpClient, user) + if err != nil { + return nil, err } - if bearerAuthToken != "" { - req.Header.Set("Authorization", "Bearer "+bearerAuthToken) + httpClient, err = c.bearerAuthAuth(req, httpClient, user) + if err != nil { + return nil, err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -150,7 +80,7 @@ func (c *Client) ListAdminUsers(ctx context.Context, apiKeyHeaderToken string, b if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out []User @@ -164,24 +94,7 @@ func (c *Client) ListAdminUsers(ctx context.Context, apiKeyHeaderToken string, b // QueryDataWithApiKey // Query data with API key // Accepts API key in header, query parameter, or cookie -func (c *Client) QueryDataWithApiKey(ctx context.Context, apiKeyCookieToken string, apiKeyHeaderToken string, apiKeyQueryToken string, rawToken string, query *string) error { - if apiKeyCookieToken == "" { - apiKeyCookieToken = c.apiKeyCookieToken - } - if apiKeyHeaderToken == "" { - apiKeyHeaderToken = c.apiKeyHeaderToken - } - if apiKeyQueryToken == "" { - apiKeyQueryToken = c.apiKeyQueryToken - } - if rawToken == "" { - rawToken = c.rawToken - } - - if !((apiKeyHeaderToken != "") || (apiKeyQueryToken != "") || (apiKeyCookieToken != "") || (rawToken != "")) { - return ErrMissingAuthToken - } - +func (c *Client) QueryDataWithApiKey(ctx context.Context, user *example.ExampleAuth, query *string) error { u := c.baseURL + "/data/query" queryParams := url.Values{} if query != nil { @@ -196,22 +109,26 @@ func (c *Client) QueryDataWithApiKey(ctx context.Context, apiKeyCookieToken stri if err != nil { return err } - if apiKeyCookieToken != "" { - req.AddCookie(&http.Cookie{Name: "cookie_name", Value: apiKeyCookieToken}) + + httpClient := c.httpClient + httpClient, err = c.apiKeyCookieAuth(req, httpClient, user) + if err != nil { + return err } - if apiKeyHeaderToken != "" { - req.Header.Set("X-API-Key", apiKeyHeaderToken) + httpClient, err = c.apiKeyHeaderAuth(req, httpClient, user) + if err != nil { + return err } - if apiKeyQueryToken != "" { - q := req.URL.Query() - q.Set("query_key_name", apiKeyQueryToken) - req.URL.RawQuery = q.Encode() + httpClient, err = c.apiKeyQueryAuth(req, httpClient, user) + if err != nil { + return err } - if rawToken != "" { - req.Header.Set("Authorization", rawToken) + httpClient, err = c.rawAuth(req, httpClient, user) + if err != nil { + return err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -220,7 +137,7 @@ func (c *Client) QueryDataWithApiKey(ctx context.Context, apiKeyCookieToken stri if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil @@ -229,27 +146,21 @@ func (c *Client) QueryDataWithApiKey(ctx context.Context, apiKeyCookieToken stri // ListDocuments // List documents // Requires basic authentication -func (c *Client) ListDocuments(ctx context.Context, basicAuthUsername string, basicAuthPassword string) error { - if basicAuthUsername == "" { - basicAuthUsername = c.basicAuthUsername - basicAuthPassword = c.basicAuthPassword - } - - if !(basicAuthUsername != "") { - return ErrMissingAuthToken - } - +func (c *Client) ListDocuments(ctx context.Context, user *example.ExampleAuth) error { u := c.baseURL + "/documents" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return err } - if basicAuthUsername != "" { - req.SetBasicAuth(basicAuthUsername, basicAuthPassword) + + httpClient := c.httpClient + httpClient, err = c.basicAuthAuth(req, httpClient, user) + if err != nil { + return err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -258,7 +169,7 @@ func (c *Client) ListDocuments(ctx context.Context, basicAuthUsername string, ba if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil @@ -267,32 +178,25 @@ func (c *Client) ListDocuments(ctx context.Context, basicAuthUsername string, ba // CreateDocument // Create document // Requires API key with bearer token -func (c *Client) CreateDocument(ctx context.Context, apiKeyHeaderToken string, bearerAuthToken string) error { - if apiKeyHeaderToken == "" { - apiKeyHeaderToken = c.apiKeyHeaderToken - } - if bearerAuthToken == "" { - bearerAuthToken = c.bearerAuthToken - } - - if !(apiKeyHeaderToken != "" && bearerAuthToken != "") { - return ErrMissingAuthToken - } - +func (c *Client) CreateDocument(ctx context.Context, user *example.ExampleAuth) error { u := c.baseURL + "/documents" req, err := http.NewRequestWithContext(ctx, "POST", u, http.NoBody) if err != nil { return err } - if apiKeyHeaderToken != "" { - req.Header.Set("X-API-Key", apiKeyHeaderToken) + + httpClient := c.httpClient + httpClient, err = c.apiKeyHeaderAuth(req, httpClient, user) + if err != nil { + return err } - if bearerAuthToken != "" { - req.Header.Set("Authorization", "Bearer "+bearerAuthToken) + httpClient, err = c.bearerAuthAuth(req, httpClient, user) + if err != nil { + return err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -301,7 +205,7 @@ func (c *Client) CreateDocument(ctx context.Context, apiKeyHeaderToken string, b if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil @@ -310,23 +214,21 @@ func (c *Client) CreateDocument(ctx context.Context, apiKeyHeaderToken string, b // Overview // System overview // Requires OAuth client credentials -func (c *Client) Overview(ctx context.Context) error { - if !(c.oauth2ClientCredentialsExampleConfig != nil) { - return ErrMissingAuthToken - } - +func (c *Client) Overview(ctx context.Context, user *example.ExampleAuth) error { u := c.baseURL + "/overview" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return err } - doer := c.doer - if c.oauth2ClientCredentialsExampleConfig != nil { - doer = c.oauth2ClientCredentialsExampleConfig.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer)) + + httpClient := c.httpClient + httpClient, err = c.oauth2ClientCredentialsExampleAuth(req, httpClient, user) + if err != nil { + return err } - resp, err := doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -335,7 +237,7 @@ func (c *Client) Overview(ctx context.Context) error { if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil @@ -344,23 +246,21 @@ func (c *Client) Overview(ctx context.Context) error { // GetDetailedProfile // Get detailed profile // Requires OpenID Connect authentication -func (c *Client) GetDetailedProfile(ctx context.Context, openIdconnectToken *oauth2.Token) error { - if !(openIdconnectToken != nil) { - return ErrMissingAuthToken - } - +func (c *Client) GetDetailedProfile(ctx context.Context, user *example.ExampleAuth) error { u := c.baseURL + "/profile/detailed" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return err } - doer := c.doer - if openIdconnectToken != nil { - doer = c.openIdconnectConfig.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer), openIdconnectToken) + + httpClient := c.httpClient + httpClient, err = c.openIdconnectAuth(req, httpClient, user) + if err != nil { + return err } - resp, err := doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -369,7 +269,7 @@ func (c *Client) GetDetailedProfile(ctx context.Context, openIdconnectToken *oau if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil @@ -382,45 +282,33 @@ func (c *Client) GetDetailedProfile(ctx context.Context, openIdconnectToken *oau // 2. API key (header) + Basic auth, OR // 3. Bearer token + API // key (cookie) -func (c *Client) GetProtectedResource(ctx context.Context, apiKeyCookieToken string, apiKeyHeaderToken string, basicAuthUsername string, basicAuthPassword string, bearerAuthToken string) error { - if apiKeyCookieToken == "" { - apiKeyCookieToken = c.apiKeyCookieToken - } - if apiKeyHeaderToken == "" { - apiKeyHeaderToken = c.apiKeyHeaderToken - } - if basicAuthUsername == "" { - basicAuthUsername = c.basicAuthUsername - basicAuthPassword = c.basicAuthPassword - } - if bearerAuthToken == "" { - bearerAuthToken = c.bearerAuthToken - } - - if !((apiKeyHeaderToken != "" && basicAuthUsername != "") || (apiKeyCookieToken != "" && bearerAuthToken != "")) { - return ErrMissingAuthToken - } - +func (c *Client) GetProtectedResource(ctx context.Context, user *example.ExampleAuth) error { u := c.baseURL + "/protected-resource" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return err } - if apiKeyCookieToken != "" { - req.AddCookie(&http.Cookie{Name: "cookie_name", Value: apiKeyCookieToken}) + + httpClient := c.httpClient + httpClient, err = c.apiKeyCookieAuth(req, httpClient, user) + if err != nil { + return err } - if apiKeyHeaderToken != "" { - req.Header.Set("X-API-Key", apiKeyHeaderToken) + httpClient, err = c.apiKeyHeaderAuth(req, httpClient, user) + if err != nil { + return err } - if basicAuthUsername != "" { - req.SetBasicAuth(basicAuthUsername, basicAuthPassword) + httpClient, err = c.basicAuthAuth(req, httpClient, user) + if err != nil { + return err } - if bearerAuthToken != "" { - req.Header.Set("Authorization", "Bearer "+bearerAuthToken) + httpClient, err = c.bearerAuthAuth(req, httpClient, user) + if err != nil { + return err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -429,7 +317,7 @@ func (c *Client) GetProtectedResource(ctx context.Context, apiKeyCookieToken str if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil @@ -446,7 +334,9 @@ func (c *Client) GetPublicStatus(ctx context.Context) (*GetPublicStatusResponse, return nil, err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -455,7 +345,7 @@ func (c *Client) GetPublicStatus(ctx context.Context) (*GetPublicStatusResponse, if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out GetPublicStatusResponse @@ -469,36 +359,29 @@ func (c *Client) GetPublicStatus(ctx context.Context) (*GetPublicStatusResponse, // GetCurrentUser // Get current user // Requires either API key in header OR bearer token -func (c *Client) GetCurrentUser(ctx context.Context, apiKeyHeaderToken string, bearerAuthToken string, oauth2ExampleToken *oauth2.Token) (*User, error) { - if apiKeyHeaderToken == "" { - apiKeyHeaderToken = c.apiKeyHeaderToken - } - if bearerAuthToken == "" { - bearerAuthToken = c.bearerAuthToken - } - - if !((apiKeyHeaderToken != "") || (bearerAuthToken != "") || (oauth2ExampleToken != nil)) { - return nil, ErrMissingAuthToken - } - +func (c *Client) GetCurrentUser(ctx context.Context, user *example.ExampleAuth) (*User, error) { u := c.baseURL + "/users/me" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return nil, err } - if apiKeyHeaderToken != "" { - req.Header.Set("X-API-Key", apiKeyHeaderToken) + + httpClient := c.httpClient + httpClient, err = c.apiKeyHeaderAuth(req, httpClient, user) + if err != nil { + return nil, err } - if bearerAuthToken != "" { - req.Header.Set("Authorization", "Bearer "+bearerAuthToken) + httpClient, err = c.bearerAuthAuth(req, httpClient, user) + if err != nil { + return nil, err } - doer := c.doer - if oauth2ExampleToken != nil { - doer = c.oauth2ExampleConfig.Client(context.WithValue(ctx, oauth2.HTTPClient, c.doer), oauth2ExampleToken) + httpClient, err = c.oauth2ExampleAuth(req, httpClient, user) + if err != nil { + return nil, err } - resp, err := doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -507,7 +390,7 @@ func (c *Client) GetCurrentUser(ctx context.Context, apiKeyHeaderToken string, b if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out User @@ -521,26 +404,21 @@ func (c *Client) GetCurrentUser(ctx context.Context, apiKeyHeaderToken string, b // GetUserProfile // Get user profile // Requires bearer token authentication -func (c *Client) GetUserProfile(ctx context.Context, bearerAuthToken string) (*User, error) { - if bearerAuthToken == "" { - bearerAuthToken = c.bearerAuthToken - } - - if !(bearerAuthToken != "") { - return nil, ErrMissingAuthToken - } - +func (c *Client) GetUserProfile(ctx context.Context, user *example.ExampleAuth) (*User, error) { u := c.baseURL + "/users/profile" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return nil, err } - if bearerAuthToken != "" { - req.Header.Set("Authorization", "Bearer "+bearerAuthToken) + + httpClient := c.httpClient + httpClient, err = c.bearerAuthAuth(req, httpClient, user) + if err != nil { + return nil, err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -549,7 +427,7 @@ func (c *Client) GetUserProfile(ctx context.Context, bearerAuthToken string) (*U if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out User diff --git a/tests/auth/openapi.yaml b/tests/auth/openapi.yaml index 9d034eb..3766e97 100644 --- a/tests/auth/openapi.yaml +++ b/tests/auth/openapi.yaml @@ -61,6 +61,7 @@ components: # Custom auth that processes the request directly using `x-raw-auth`. Raw: x-raw-auth: true + x-raw-client-auth: true type: apiKey name: Authorization in: header diff --git a/tests/csvresponse/foji.yaml b/tests/csvresponse/foji.yaml index 0d73a23..0b01912 100644 --- a/tests/csvresponse/foji.yaml +++ b/tests/csvresponse/foji.yaml @@ -7,6 +7,7 @@ processes: params: Package: foji/tests/csvresponse Auth: ExampleAuth + ClientAuth: ExampleAuth OpenAPIFile: 'tests/csvresponse/http_handler_gen.go': foji/openapi/handler.go.tpl 'tests/csvresponse/http_client_gen.go': foji/openapi/client.go.tpl diff --git a/tests/csvresponse/http_client_gen.go b/tests/csvresponse/http_client_gen.go index 9b065c7..2b8c818 100644 --- a/tests/csvresponse/http_client_gen.go +++ b/tests/csvresponse/http_client_gen.go @@ -4,52 +4,35 @@ package csvresponse import ( "context" - "errors" "fmt" "io" "net/http" -) -type Doer interface { - Do(*http.Request) (*http.Response, error) -} + "github.com/bir/iken/httputil" +) -type ClientOption func(*Client) +type ( + ClientAuthenticator = httputil.ClientAuthenticateFunc[*ExampleAuth] + ClientTokenAuthenticator = httputil.ClientTokenAuthenticatorFunc[*ExampleAuth] + ClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*ExampleAuth] + ClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*ExampleAuth] + ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*ExampleAuth] +) type Client struct { - baseURL string - doer Doer - headerAuthToken string -} - -func NewClient(baseURL string, doer Doer, opts ...ClientOption) *Client { - c := &Client{baseURL: baseURL, doer: doer} - - for _, opt := range opts { - opt(c) - } - - return c + baseURL string + httpClient *http.Client + headerAuthAuth ClientAuthenticator } -func WithHeaderAuthToken(token string) ClientOption { - return func(c *Client) { - c.headerAuthToken = token +func NewClient(baseURL string, httpClient *http.Client, headerAuthAuth ClientTokenAuthenticator) *Client { + return &Client{ + baseURL: baseURL, + httpClient: httpClient, + headerAuthAuth: httputil.HeaderClientAuth("Authorization", headerAuthAuth), } } -var ErrMissingAuthToken = errors.New("missing auth token") - -type APIError struct { - StatusCode int - Status string - Body []byte -} - -func (e *APIError) Error() string { - return fmt.Sprintf("%d %s: %s", e.StatusCode, e.Status, string(e.Body)) -} - // GetByteCsv func (c *Client) GetByteCsv(ctx context.Context) ([]byte, error) { u := c.baseURL + "/bytesCSV" @@ -59,7 +42,9 @@ func (c *Client) GetByteCsv(ctx context.Context) ([]byte, error) { return nil, err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -68,7 +53,7 @@ func (c *Client) GetByteCsv(ctx context.Context) ([]byte, error) { if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } out, err := io.ReadAll(resp.Body) @@ -88,7 +73,9 @@ func (c *Client) GetReaderCsv(ctx context.Context) (io.Reader, error) { return nil, err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -97,7 +84,7 @@ func (c *Client) GetReaderCsv(ctx context.Context) (io.Reader, error) { defer resp.Body.Close() errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return resp.Body, nil @@ -112,7 +99,9 @@ func (c *Client) GetStringCsv(ctx context.Context) (string, error) { return "", err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return "", err } @@ -121,7 +110,7 @@ func (c *Client) GetStringCsv(ctx context.Context) (string, error) { if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return "", &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return "", httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } out, err := io.ReadAll(resp.Body) diff --git a/tests/example/foji.yaml b/tests/example/foji.yaml index 0702b13..ad8778a 100644 --- a/tests/example/foji.yaml +++ b/tests/example/foji.yaml @@ -7,6 +7,7 @@ processes: params: Package: foji/tests/example Auth: ExampleAuth + ClientAuth: ExampleAuth OpenAPIFile: tests/example/http_handler_gen.go: foji/openapi/handler.go.tpl tests/example/http_client_gen.go: foji/openapi/client.go.tpl diff --git a/tests/example/http_client_gen.go b/tests/example/http_client_gen.go index d92f9d9..8f65b64 100644 --- a/tests/example/http_client_gen.go +++ b/tests/example/http_client_gen.go @@ -6,7 +6,6 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "io" "mime/multipart" @@ -20,74 +19,36 @@ import ( "github.com/google/uuid" ) -type Doer interface { - Do(*http.Request) (*http.Response, error) -} - -type ClientOption func(*Client) +type ( + ClientAuthenticator = httputil.ClientAuthenticateFunc[*ExampleAuth] + ClientTokenAuthenticator = httputil.ClientTokenAuthenticatorFunc[*ExampleAuth] + ClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*ExampleAuth] + ClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*ExampleAuth] + ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*ExampleAuth] +) type Client struct { - baseURL string - doer Doer - bearerToken string - customHeaderAuthToken string - headerAuthToken string - jwtToken string - rawToken string -} - -func NewClient(baseURL string, doer Doer, opts ...ClientOption) *Client { - c := &Client{baseURL: baseURL, doer: doer} - - for _, opt := range opts { - opt(c) - } - - return c -} - -func WithBearerToken(token string) ClientOption { - return func(c *Client) { - c.bearerToken = token - } -} - -func WithCustomHeaderAuthToken(token string) ClientOption { - return func(c *Client) { - c.customHeaderAuthToken = token - } -} - -func WithHeaderAuthToken(token string) ClientOption { - return func(c *Client) { - c.headerAuthToken = token - } -} - -func WithJwtToken(token string) ClientOption { - return func(c *Client) { - c.jwtToken = token - } + baseURL string + httpClient *http.Client + bearerAuth ClientAuthenticator + customHeaderAuthAuth ClientAuthenticator + headerAuthAuth ClientAuthenticator + jwtAuth ClientAuthenticator + rawAuth ClientAuthenticator } -func WithRawToken(token string) ClientOption { - return func(c *Client) { - c.rawToken = token +func NewClient(baseURL string, httpClient *http.Client, bearerAuth ClientTokenAuthenticator, customHeaderAuthAuth ClientTokenAuthenticator, headerAuthAuth ClientTokenAuthenticator, jwtAuth ClientTokenAuthenticator, rawAuth ClientTokenAuthenticator) *Client { + return &Client{ + baseURL: baseURL, + httpClient: httpClient, + bearerAuth: httputil.BearerClientAuth("Authorization", bearerAuth), + customHeaderAuthAuth: httputil.HeaderClientAuth("X-CUSTOM-HEADER", customHeaderAuthAuth), + headerAuthAuth: httputil.HeaderClientAuth("Authorization", headerAuthAuth), + jwtAuth: httputil.QueryClientAuth("jwt", jwtAuth), + rawAuth: httputil.HeaderClientAuth("Authorization", rawAuth), } } -var ErrMissingAuthToken = errors.New("missing auth token") - -type APIError struct { - StatusCode int - Status string - Body []byte -} - -func (e *APIError) Error() string { - return fmt.Sprintf("%d %s: %s", e.StatusCode, e.Status, string(e.Body)) -} - // GetExamples func (c *Client) GetExamples(ctx context.Context) (*Examples, error) { u := c.baseURL + "/examples" @@ -97,7 +58,9 @@ func (c *Client) GetExamples(ctx context.Context) (*Examples, error) { return nil, err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -106,7 +69,7 @@ func (c *Client) GetExamples(ctx context.Context) (*Examples, error) { if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out Examples @@ -118,34 +81,25 @@ func (c *Client) GetExamples(ctx context.Context) (*Examples, error) { } // GetAuthComplex -func (c *Client) GetAuthComplex(ctx context.Context, headerAuthToken string, jwtToken string) error { - if headerAuthToken == "" { - headerAuthToken = c.headerAuthToken - } - if jwtToken == "" { - jwtToken = c.jwtToken - } - - if !((headerAuthToken != "") || (jwtToken != "")) { - return ErrMissingAuthToken - } - +func (c *Client) GetAuthComplex(ctx context.Context, user *ExampleAuth) error { u := c.baseURL + "/examples/auth/complex" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return err } - if headerAuthToken != "" { - req.Header.Set("Authorization", headerAuthToken) + + httpClient := c.httpClient + httpClient, err = c.headerAuthAuth(req, httpClient, user) + if err != nil { + return err } - if jwtToken != "" { - q := req.URL.Query() - q.Set("jwt", jwtToken) - req.URL.RawQuery = q.Encode() + httpClient, err = c.jwtAuth(req, httpClient, user) + if err != nil { + return err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -154,33 +108,28 @@ func (c *Client) GetAuthComplex(ctx context.Context, headerAuthToken string, jwt if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil } // GetAuthSimple -func (c *Client) GetAuthSimple(ctx context.Context, headerAuthToken string) error { - if headerAuthToken == "" { - headerAuthToken = c.headerAuthToken - } - - if !(headerAuthToken != "") { - return ErrMissingAuthToken - } - +func (c *Client) GetAuthSimple(ctx context.Context, user *ExampleAuth) error { u := c.baseURL + "/examples/auth/simple" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return err } - if headerAuthToken != "" { - req.Header.Set("Authorization", headerAuthToken) + + httpClient := c.httpClient + httpClient, err = c.headerAuthAuth(req, httpClient, user) + if err != nil { + return err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -189,29 +138,28 @@ func (c *Client) GetAuthSimple(ctx context.Context, headerAuthToken string) erro if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil } // GetAuthSimpleMaybe -func (c *Client) GetAuthSimpleMaybe(ctx context.Context, headerAuthToken string) error { - if headerAuthToken == "" { - headerAuthToken = c.headerAuthToken - } - +func (c *Client) GetAuthSimpleMaybe(ctx context.Context, user *ExampleAuth) error { u := c.baseURL + "/examples/auth/simple/maybe" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return err } - if headerAuthToken != "" { - req.Header.Set("Authorization", headerAuthToken) + + httpClient := c.httpClient + httpClient, err = c.headerAuthAuth(req, httpClient, user) + if err != nil { + return err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -220,33 +168,28 @@ func (c *Client) GetAuthSimpleMaybe(ctx context.Context, headerAuthToken string) if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil } // GetAuthSimple2 -func (c *Client) GetAuthSimple2(ctx context.Context, headerAuthToken string) error { - if headerAuthToken == "" { - headerAuthToken = c.headerAuthToken - } - - if !(headerAuthToken != "") { - return ErrMissingAuthToken - } - +func (c *Client) GetAuthSimple2(ctx context.Context, user *ExampleAuth) error { u := c.baseURL + "/examples/auth/simple2" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return err } - if headerAuthToken != "" { - req.Header.Set("Authorization", headerAuthToken) + + httpClient := c.httpClient + httpClient, err = c.headerAuthAuth(req, httpClient, user) + if err != nil { + return err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -255,29 +198,28 @@ func (c *Client) GetAuthSimple2(ctx context.Context, headerAuthToken string) err if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil } // GetAuthSimple2Maybe -func (c *Client) GetAuthSimple2Maybe(ctx context.Context, headerAuthToken string) error { - if headerAuthToken == "" { - headerAuthToken = c.headerAuthToken - } - +func (c *Client) GetAuthSimple2Maybe(ctx context.Context, user *ExampleAuth) error { u := c.baseURL + "/examples/auth/simple2/maybe" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return err } - if headerAuthToken != "" { - req.Header.Set("Authorization", headerAuthToken) + + httpClient := c.httpClient + httpClient, err = c.headerAuthAuth(req, httpClient, user) + if err != nil { + return err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -286,37 +228,32 @@ func (c *Client) GetAuthSimple2Maybe(ctx context.Context, headerAuthToken string if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil } // GetAuthComplexMaybe -func (c *Client) GetAuthComplexMaybe(ctx context.Context, headerAuthToken string, jwtToken string) error { - if headerAuthToken == "" { - headerAuthToken = c.headerAuthToken - } - if jwtToken == "" { - jwtToken = c.jwtToken - } - +func (c *Client) GetAuthComplexMaybe(ctx context.Context, user *ExampleAuth) error { u := c.baseURL + "/examples/complexAuthMaybe" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return err } - if headerAuthToken != "" { - req.Header.Set("Authorization", headerAuthToken) + + httpClient := c.httpClient + httpClient, err = c.headerAuthAuth(req, httpClient, user) + if err != nil { + return err } - if jwtToken != "" { - q := req.URL.Query() - q.Set("jwt", jwtToken) - req.URL.RawQuery = q.Encode() + httpClient, err = c.jwtAuth(req, httpClient, user) + if err != nil { + return err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return err } @@ -325,45 +262,36 @@ func (c *Client) GetAuthComplexMaybe(ctx context.Context, headerAuthToken string if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil } // GetComplexSecurity -func (c *Client) GetComplexSecurity(ctx context.Context, bearerToken string, customHeaderAuthToken string, rawToken string) ([]TestInt, error) { - if bearerToken == "" { - bearerToken = c.bearerToken - } - if customHeaderAuthToken == "" { - customHeaderAuthToken = c.customHeaderAuthToken - } - if rawToken == "" { - rawToken = c.rawToken - } - - if !((rawToken != "") || (bearerToken != "") || (customHeaderAuthToken != "")) { - return nil, ErrMissingAuthToken - } - +func (c *Client) GetComplexSecurity(ctx context.Context, user *ExampleAuth) ([]TestInt, error) { u := c.baseURL + "/examples/complexSecurity" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) if err != nil { return nil, err } - if bearerToken != "" { - req.Header.Set("Authorization", "Bearer "+bearerToken) + + httpClient := c.httpClient + httpClient, err = c.bearerAuth(req, httpClient, user) + if err != nil { + return nil, err } - if customHeaderAuthToken != "" { - req.Header.Set("X-CUSTOM-HEADER", customHeaderAuthToken) + httpClient, err = c.customHeaderAuthAuth(req, httpClient, user) + if err != nil { + return nil, err } - if rawToken != "" { - req.Header.Set("Authorization", rawToken) + httpClient, err = c.rawAuth(req, httpClient, user) + if err != nil { + return nil, err } - resp, err := c.doer.Do(req) + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -372,7 +300,7 @@ func (c *Client) GetComplexSecurity(ctx context.Context, bearerToken string, cus if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out []TestInt @@ -452,7 +380,9 @@ func (c *Client) AddForm(ctx context.Context, body AddFormRequest) (*FooBar, err req.Header.Set("Content-Type", contentType) - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -461,7 +391,7 @@ func (c *Client) AddForm(ctx context.Context, body AddFormRequest) (*FooBar, err if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out FooBar @@ -537,7 +467,9 @@ func (c *Client) AddMultipartForm(ctx context.Context, body AddMultipartFormRequ req.Header.Set("Content-Type", contentType) - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -546,7 +478,7 @@ func (c *Client) AddMultipartForm(ctx context.Context, body AddMultipartFormRequ if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out FooBar @@ -567,7 +499,9 @@ func (c *Client) HeaderResponse(ctx context.Context) error { return err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return err } @@ -576,7 +510,7 @@ func (c *Client) HeaderResponse(ctx context.Context) error { if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil @@ -601,7 +535,9 @@ func (c *Client) AddInlinedAllOf(ctx context.Context, body AddInlinedAllOfReques req.Header.Set("Content-Type", contentType) - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -610,7 +546,7 @@ func (c *Client) AddInlinedAllOf(ctx context.Context, body AddInlinedAllOfReques if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out FooBar @@ -640,7 +576,9 @@ func (c *Client) AddInlinedBody(ctx context.Context, body AddInlinedBodyRequest) req.Header.Set("Content-Type", contentType) - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -649,7 +587,7 @@ func (c *Client) AddInlinedBody(ctx context.Context, body AddInlinedBodyRequest) if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out FooBar @@ -680,7 +618,9 @@ func (c *Client) GetExampleParams(ctx context.Context, k1 string, k2 uuid.UUID, return nil, err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -689,7 +629,7 @@ func (c *Client) GetExampleParams(ctx context.Context, k1 string, k2 uuid.UUID, if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out Example @@ -719,7 +659,9 @@ func (c *Client) NoResponse(ctx context.Context, body Foo) error { req.Header.Set("Content-Type", contentType) - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return err } @@ -728,7 +670,7 @@ func (c *Client) NoResponse(ctx context.Context, body Foo) error { if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } return nil @@ -764,7 +706,9 @@ func (c *Client) GetExampleOptional(ctx context.Context, k1 *string, k2 *uuid.UU return nil, err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -773,7 +717,7 @@ func (c *Client) GetExampleOptional(ctx context.Context, k1 *string, k2 *uuid.UU if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out Example @@ -809,7 +753,9 @@ func (c *Client) GetExampleQuery(ctx context.Context, k1 string, k2 uuid.UUID, k return nil, err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -818,7 +764,7 @@ func (c *Client) GetExampleQuery(ctx context.Context, k1 string, k2 uuid.UUID, k if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out Example @@ -848,7 +794,9 @@ func (c *Client) GetRawBody(ctx context.Context, body Foo) (*Example, error) { req.Header.Set("Content-Type", contentType) - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -857,7 +805,7 @@ func (c *Client) GetRawBody(ctx context.Context, body Foo) (*Example, error) { if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out Example @@ -883,7 +831,9 @@ func (c *Client) GetRawRequest(ctx context.Context, vehicle GetRawRequestVehicle return nil, err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -892,7 +842,7 @@ func (c *Client) GetRawRequest(ctx context.Context, vehicle GetRawRequestVehicle if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out Example @@ -918,7 +868,9 @@ func (c *Client) GetRawRequestResponse(ctx context.Context, vehicle GetRawReques return nil, err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -927,7 +879,7 @@ func (c *Client) GetRawRequestResponse(ctx context.Context, vehicle GetRawReques if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out Example @@ -953,7 +905,9 @@ func (c *Client) GetRawRequestResponseAndHeaders(ctx context.Context, vehicle Ge return nil, err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -962,7 +916,7 @@ func (c *Client) GetRawRequestResponseAndHeaders(ctx context.Context, vehicle Ge if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out Example @@ -988,7 +942,9 @@ func (c *Client) GetRawResponse(ctx context.Context, vehicle GetRawResponseVehic return nil, err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -997,7 +953,7 @@ func (c *Client) GetRawResponse(ctx context.Context, vehicle GetRawResponseVehic if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out Example @@ -1028,7 +984,9 @@ func (c *Client) GetTest(ctx context.Context, vehicle GetTestVehicle, vehicleDef return nil, err } - resp, err := c.doer.Do(req) + httpClient := c.httpClient + + resp, err := httpClient.Do(req) if err != nil { return nil, err } @@ -1037,7 +995,7 @@ func (c *Client) GetTest(ctx context.Context, vehicle GetTestVehicle, vehicleDef if resp.StatusCode < 200 || resp.StatusCode >= 300 { errBody, _ := io.ReadAll(resp.Body) - return nil, &APIError{StatusCode: resp.StatusCode, Status: resp.Status, Body: errBody} + return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } var out Example diff --git a/tests/go.mod b/tests/go.mod index dba2e32..2093f3d 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -6,7 +6,6 @@ require ( github.com/bir/iken v0.8.12 github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.11.1 - golang.org/x/oauth2 v0.36.0 ) require ( diff --git a/tests/go.sum b/tests/go.sum index f564104..f857f08 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -1,5 +1,3 @@ -github.com/bir/iken v0.8.12 h1:ROLRu5f8KIYSuKcSLzOrpbpaNLQHzzOUnYBihijvMzk= -github.com/bir/iken v0.8.12/go.mod h1:F3gTwkXjHAJ0thzPYo2iOk+Ot35TOv4vmGq9JdEIlG0= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -22,8 +20,6 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= -golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/tests/tests_main.go b/tests/tests_main.go index 1797cb5..605731d 100644 --- a/tests/tests_main.go +++ b/tests/tests_main.go @@ -26,6 +26,30 @@ func authorize(ctx context.Context, user *example.ExampleAuth, scopes []string) return nil } +func csvClientToken(ctx context.Context, user *csvresponse.ExampleAuth) (string, error) { + return "", nil +} + +func clientToken(ctx context.Context, user *example.ExampleAuth) (string, error) { + return "", nil +} + +func clientBasic(ctx context.Context, user *example.ExampleAuth) (string, string, error) { + return "", "", nil +} + +func clientCookie(ctx context.Context, user *example.ExampleAuth) (*http.Cookie, error) { + return nil, nil +} + +func clientWrap(ctx context.Context, inner *http.Client, user *example.ExampleAuth) (*http.Client, error) { + return inner, nil +} + +func clientRaw(r *http.Request, inner *http.Client, user *example.ExampleAuth) (*http.Client, error) { + return inner, nil +} + func main() { // Requires the generated Operations to match the Service layer var _ csvresponse.Operations = &csvresponse.Service{} @@ -36,5 +60,10 @@ func main() { var authOps auth.Operations = &auth.Service{} auth.RegisterHTTP(authOps, http.NewServeMux(), tokenAuth, tokenAuth, tokenAuth, basicAuth, tokenAuth, rawAuth, rawAuth, rawAuth, rawAuth, authorize) + // Requires the generated clients to construct with authenticators matching each security scheme. + _ = csvresponse.NewClient("http://localhost", http.DefaultClient, csvClientToken) + _ = example.NewClient("http://localhost", http.DefaultClient, clientToken, clientToken, clientToken, clientToken, clientToken) + _ = auth.NewClient("http://localhost", http.DefaultClient, clientCookie, clientToken, clientToken, clientBasic, clientToken, clientWrap, clientWrap, clientWrap, clientRaw) + os.Exit(0) } From a0e2f4c66b580eecdc18f1d92cb97d1239f64bce Mon Sep 17 00:00:00 2001 From: David Newgas Date: Fri, 10 Jul 2026 10:47:44 -0700 Subject: [PATCH 4/9] Support authorizing ust one security group Motivation: OpenAPI operations may specify several security requirements, each of which consists of several security schemas. A invocation must fulfil all the security schemas of at least one security requirement. In addition, it's not possible to have a "no op" authenticator as even a empty token/cookie/user/pass would get encoded into the request. Faced with having to explicitly handle authenticators not being able to add auth to a request, we choose to fully respect the spec's hierachy of security groups. Change: Using `iken/httputil.ClientSecurityGroups`, requests for operations with more than one security requirement will now be authorized according to the first security requirement that has all of it's security schemas' authorizers run without error. If no security requirement can be met, the request will not be sent and an error will be returned. --- foji/openapi/client.go.tpl | 39 ++++++++++++- tests/auth/http_client_gen.go | 87 +++++++++++++--------------- tests/csvresponse/http_client_gen.go | 4 +- tests/example/http_client_gen.go | 80 +++++++++++++++---------- tests/go.sum | 2 + 5 files changed, 135 insertions(+), 77 deletions(-) diff --git a/foji/openapi/client.go.tpl b/foji/openapi/client.go.tpl index 25d2e31..51819a7 100644 --- a/foji/openapi/client.go.tpl +++ b/foji/openapi/client.go.tpl @@ -118,6 +118,11 @@ type ( ClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*{{ $.CheckPackage $clientAuth $package }}] ClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*{{ $.CheckPackage $clientAuth $package }}] ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*{{ $.CheckPackage $clientAuth $package }}] + +{{- if .HasComplexAuth }} + ClientSecurityGroup = httputil.ClientSecurityGroup[*{{ $.CheckPackage $clientAuth $package }}] + ClientSecurityGroups = httputil.ClientSecurityGroups[*{{ $.CheckPackage $clientAuth $package }}] +{{- end }} ) {{ end -}} @@ -128,15 +133,40 @@ type Client struct { {{- range $security, $value := .API.Components.SecuritySchemes }} {{ camel $security }}Auth ClientAuthenticator {{- end }} +{{- range $name, $path := .API.Paths.Map }} + {{- range $verb, $op := $path.Operations }} + {{- if not ($.IsSimpleAuth $op) }} + {{ camel $op.OperationID}}Security ClientSecurityGroups + {{- end}} + {{- end}} +{{- end}} } func NewClient(baseURL string, httpClient *http.Client {{- range $security, $value := .API.Components.SecuritySchemes }}, {{ template "clientAuth" ($.WithParams "scheme" $security "mode" "param") }}{{- end }}) *Client { - return &Client{ + c := &Client{ baseURL: baseURL, httpClient: httpClient, {{- range $security, $value := .API.Components.SecuritySchemes }}{{ template "clientAuth" ($.WithParams "scheme" $security "mode" "construct") }}{{- end }} } +{{- range $name, $path := .API.Paths.Map }} + {{- range $verb, $op := $path.Operations }} + {{- if not ($.IsSimpleAuth $op) }} + + c.{{ camel $op.OperationID}}Security = ClientSecurityGroups{ + {{- range $securityGroup := $.OpSecurity $op }} + ClientSecurityGroup{ + {{- range $security, $scopes := $securityGroup -}} + c.{{ camel $security }}Auth, + {{- end -}} + }, + {{- end }} + } + {{- end}} + {{- end}} +{{- end}} + + return c } {{- range $name, $path := .API.Paths.Map }} @@ -322,7 +352,14 @@ func (c *Client) {{ pascal $op.OperationID }}(ctx context.Context, {{- end }} httpClient := c.httpClient + {{- if $.IsSimpleAuth $op }} {{- range $scheme := $.OpSecuritySchemes $op }}{{ template "clientAuth" ($.WithParams "scheme" $scheme "mode" "apply" "errRet" $errRet) }}{{- end }} + {{- else }} + httpClient, err = c.{{ camel $op.OperationID}}Security.Auth(req, httpClient, user) + if err != nil { + return {{ $errRet }}err + } + {{- end }} resp, err := httpClient.Do(req) if err != nil { diff --git a/tests/auth/http_client_gen.go b/tests/auth/http_client_gen.go index f204041..511490a 100644 --- a/tests/auth/http_client_gen.go +++ b/tests/auth/http_client_gen.go @@ -18,6 +18,8 @@ type ( ClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*example.ExampleAuth] ClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*example.ExampleAuth] ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*example.ExampleAuth] + ClientSecurityGroup = httputil.ClientSecurityGroup[*example.ExampleAuth] + ClientSecurityGroups = httputil.ClientSecurityGroups[*example.ExampleAuth] ) type Client struct { @@ -32,10 +34,15 @@ type Client struct { oauth2ExampleAuth ClientAuthenticator openIdconnectAuth ClientAuthenticator rawAuth ClientAuthenticator + listAdminUsersSecurity ClientSecurityGroups + queryDataWithApiKeySecurity ClientSecurityGroups + createDocumentSecurity ClientSecurityGroups + getProtectedResourceSecurity ClientSecurityGroups + getCurrentUserSecurity ClientSecurityGroups } func NewClient(baseURL string, httpClient *http.Client, apiKeyCookieAuth ClientCookieAuthenticator, apiKeyHeaderAuth ClientTokenAuthenticator, apiKeyQueryAuth ClientTokenAuthenticator, basicAuthAuth ClientBasicAuthenticator, bearerAuthAuth ClientTokenAuthenticator, oauth2ClientCredentialsExampleAuth ClientWrappingAuthenticator, oauth2ExampleAuth ClientWrappingAuthenticator, openIdconnectAuth ClientWrappingAuthenticator, rawAuth ClientAuthenticator) *Client { - return &Client{ + c := &Client{ baseURL: baseURL, httpClient: httpClient, apiKeyCookieAuth: httputil.CookieClientAuth(apiKeyCookieAuth), @@ -48,6 +55,34 @@ func NewClient(baseURL string, httpClient *http.Client, apiKeyCookieAuth ClientC openIdconnectAuth: httputil.WrapClientAuth(openIdconnectAuth), rawAuth: rawAuth, } + + c.listAdminUsersSecurity = ClientSecurityGroups{ + ClientSecurityGroup{c.apiKeyHeaderAuth, c.bearerAuthAuth}, + } + + c.queryDataWithApiKeySecurity = ClientSecurityGroups{ + ClientSecurityGroup{c.apiKeyHeaderAuth}, + ClientSecurityGroup{c.apiKeyQueryAuth}, + ClientSecurityGroup{c.apiKeyCookieAuth}, + ClientSecurityGroup{c.rawAuth}, + } + + c.createDocumentSecurity = ClientSecurityGroups{ + ClientSecurityGroup{c.apiKeyHeaderAuth, c.bearerAuthAuth}, + } + + c.getProtectedResourceSecurity = ClientSecurityGroups{ + ClientSecurityGroup{c.apiKeyHeaderAuth, c.basicAuthAuth}, + ClientSecurityGroup{c.apiKeyCookieAuth, c.bearerAuthAuth}, + } + + c.getCurrentUserSecurity = ClientSecurityGroups{ + ClientSecurityGroup{c.apiKeyHeaderAuth}, + ClientSecurityGroup{c.bearerAuthAuth}, + ClientSecurityGroup{c.oauth2ExampleAuth}, + } + + return c } // ListAdminUsers @@ -62,11 +97,7 @@ func (c *Client) ListAdminUsers(ctx context.Context, user *example.ExampleAuth) } httpClient := c.httpClient - httpClient, err = c.apiKeyHeaderAuth(req, httpClient, user) - if err != nil { - return nil, err - } - httpClient, err = c.bearerAuthAuth(req, httpClient, user) + httpClient, err = c.listAdminUsersSecurity.Auth(req, httpClient, user) if err != nil { return nil, err } @@ -111,19 +142,7 @@ func (c *Client) QueryDataWithApiKey(ctx context.Context, user *example.ExampleA } httpClient := c.httpClient - httpClient, err = c.apiKeyCookieAuth(req, httpClient, user) - if err != nil { - return err - } - httpClient, err = c.apiKeyHeaderAuth(req, httpClient, user) - if err != nil { - return err - } - httpClient, err = c.apiKeyQueryAuth(req, httpClient, user) - if err != nil { - return err - } - httpClient, err = c.rawAuth(req, httpClient, user) + httpClient, err = c.queryDataWithApiKeySecurity.Auth(req, httpClient, user) if err != nil { return err } @@ -187,11 +206,7 @@ func (c *Client) CreateDocument(ctx context.Context, user *example.ExampleAuth) } httpClient := c.httpClient - httpClient, err = c.apiKeyHeaderAuth(req, httpClient, user) - if err != nil { - return err - } - httpClient, err = c.bearerAuthAuth(req, httpClient, user) + httpClient, err = c.createDocumentSecurity.Auth(req, httpClient, user) if err != nil { return err } @@ -291,19 +306,7 @@ func (c *Client) GetProtectedResource(ctx context.Context, user *example.Example } httpClient := c.httpClient - httpClient, err = c.apiKeyCookieAuth(req, httpClient, user) - if err != nil { - return err - } - httpClient, err = c.apiKeyHeaderAuth(req, httpClient, user) - if err != nil { - return err - } - httpClient, err = c.basicAuthAuth(req, httpClient, user) - if err != nil { - return err - } - httpClient, err = c.bearerAuthAuth(req, httpClient, user) + httpClient, err = c.getProtectedResourceSecurity.Auth(req, httpClient, user) if err != nil { return err } @@ -368,15 +371,7 @@ func (c *Client) GetCurrentUser(ctx context.Context, user *example.ExampleAuth) } httpClient := c.httpClient - httpClient, err = c.apiKeyHeaderAuth(req, httpClient, user) - if err != nil { - return nil, err - } - httpClient, err = c.bearerAuthAuth(req, httpClient, user) - if err != nil { - return nil, err - } - httpClient, err = c.oauth2ExampleAuth(req, httpClient, user) + httpClient, err = c.getCurrentUserSecurity.Auth(req, httpClient, user) if err != nil { return nil, err } diff --git a/tests/csvresponse/http_client_gen.go b/tests/csvresponse/http_client_gen.go index 2b8c818..cf80ba9 100644 --- a/tests/csvresponse/http_client_gen.go +++ b/tests/csvresponse/http_client_gen.go @@ -26,11 +26,13 @@ type Client struct { } func NewClient(baseURL string, httpClient *http.Client, headerAuthAuth ClientTokenAuthenticator) *Client { - return &Client{ + c := &Client{ baseURL: baseURL, httpClient: httpClient, headerAuthAuth: httputil.HeaderClientAuth("Authorization", headerAuthAuth), } + + return c } // GetByteCsv diff --git a/tests/example/http_client_gen.go b/tests/example/http_client_gen.go index 8f65b64..4e0edd9 100644 --- a/tests/example/http_client_gen.go +++ b/tests/example/http_client_gen.go @@ -25,20 +25,27 @@ type ( ClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*ExampleAuth] ClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*ExampleAuth] ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*ExampleAuth] + ClientSecurityGroup = httputil.ClientSecurityGroup[*ExampleAuth] + ClientSecurityGroups = httputil.ClientSecurityGroups[*ExampleAuth] ) type Client struct { - baseURL string - httpClient *http.Client - bearerAuth ClientAuthenticator - customHeaderAuthAuth ClientAuthenticator - headerAuthAuth ClientAuthenticator - jwtAuth ClientAuthenticator - rawAuth ClientAuthenticator + baseURL string + httpClient *http.Client + bearerAuth ClientAuthenticator + customHeaderAuthAuth ClientAuthenticator + headerAuthAuth ClientAuthenticator + jwtAuth ClientAuthenticator + rawAuth ClientAuthenticator + getAuthComplexSecurity ClientSecurityGroups + getAuthSimpleMaybeSecurity ClientSecurityGroups + getAuthSimple2MaybeSecurity ClientSecurityGroups + getAuthComplexMaybeSecurity ClientSecurityGroups + getComplexSecuritySecurity ClientSecurityGroups } func NewClient(baseURL string, httpClient *http.Client, bearerAuth ClientTokenAuthenticator, customHeaderAuthAuth ClientTokenAuthenticator, headerAuthAuth ClientTokenAuthenticator, jwtAuth ClientTokenAuthenticator, rawAuth ClientTokenAuthenticator) *Client { - return &Client{ + c := &Client{ baseURL: baseURL, httpClient: httpClient, bearerAuth: httputil.BearerClientAuth("Authorization", bearerAuth), @@ -47,6 +54,37 @@ func NewClient(baseURL string, httpClient *http.Client, bearerAuth ClientTokenAu jwtAuth: httputil.QueryClientAuth("jwt", jwtAuth), rawAuth: httputil.HeaderClientAuth("Authorization", rawAuth), } + + c.getAuthComplexSecurity = ClientSecurityGroups{ + ClientSecurityGroup{c.headerAuthAuth}, + ClientSecurityGroup{c.headerAuthAuth}, + ClientSecurityGroup{c.jwtAuth}, + } + + c.getAuthSimpleMaybeSecurity = ClientSecurityGroups{ + ClientSecurityGroup{c.headerAuthAuth}, + ClientSecurityGroup{}, + } + + c.getAuthSimple2MaybeSecurity = ClientSecurityGroups{ + ClientSecurityGroup{c.headerAuthAuth}, + ClientSecurityGroup{c.headerAuthAuth}, + ClientSecurityGroup{}, + } + + c.getAuthComplexMaybeSecurity = ClientSecurityGroups{ + ClientSecurityGroup{c.headerAuthAuth}, + ClientSecurityGroup{c.jwtAuth}, + ClientSecurityGroup{}, + } + + c.getComplexSecuritySecurity = ClientSecurityGroups{ + ClientSecurityGroup{c.rawAuth}, + ClientSecurityGroup{c.bearerAuth}, + ClientSecurityGroup{c.customHeaderAuthAuth}, + } + + return c } // GetExamples @@ -90,11 +128,7 @@ func (c *Client) GetAuthComplex(ctx context.Context, user *ExampleAuth) error { } httpClient := c.httpClient - httpClient, err = c.headerAuthAuth(req, httpClient, user) - if err != nil { - return err - } - httpClient, err = c.jwtAuth(req, httpClient, user) + httpClient, err = c.getAuthComplexSecurity.Auth(req, httpClient, user) if err != nil { return err } @@ -154,7 +188,7 @@ func (c *Client) GetAuthSimpleMaybe(ctx context.Context, user *ExampleAuth) erro } httpClient := c.httpClient - httpClient, err = c.headerAuthAuth(req, httpClient, user) + httpClient, err = c.getAuthSimpleMaybeSecurity.Auth(req, httpClient, user) if err != nil { return err } @@ -214,7 +248,7 @@ func (c *Client) GetAuthSimple2Maybe(ctx context.Context, user *ExampleAuth) err } httpClient := c.httpClient - httpClient, err = c.headerAuthAuth(req, httpClient, user) + httpClient, err = c.getAuthSimple2MaybeSecurity.Auth(req, httpClient, user) if err != nil { return err } @@ -244,11 +278,7 @@ func (c *Client) GetAuthComplexMaybe(ctx context.Context, user *ExampleAuth) err } httpClient := c.httpClient - httpClient, err = c.headerAuthAuth(req, httpClient, user) - if err != nil { - return err - } - httpClient, err = c.jwtAuth(req, httpClient, user) + httpClient, err = c.getAuthComplexMaybeSecurity.Auth(req, httpClient, user) if err != nil { return err } @@ -278,15 +308,7 @@ func (c *Client) GetComplexSecurity(ctx context.Context, user *ExampleAuth) ([]T } httpClient := c.httpClient - httpClient, err = c.bearerAuth(req, httpClient, user) - if err != nil { - return nil, err - } - httpClient, err = c.customHeaderAuthAuth(req, httpClient, user) - if err != nil { - return nil, err - } - httpClient, err = c.rawAuth(req, httpClient, user) + httpClient, err = c.getComplexSecuritySecurity.Auth(req, httpClient, user) if err != nil { return nil, err } diff --git a/tests/go.sum b/tests/go.sum index f857f08..6a80f5e 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -1,3 +1,5 @@ +github.com/bir/iken v0.8.12 h1:ROLRu5f8KIYSuKcSLzOrpbpaNLQHzzOUnYBihijvMzk= +github.com/bir/iken v0.8.12/go.mod h1:F3gTwkXjHAJ0thzPYo2iOk+Ot35TOv4vmGq9JdEIlG0= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From 2ead29d6d766c077cc06c0dc480d462f2e41e15f Mon Sep 17 00:00:00 2001 From: David Newgas Date: Fri, 10 Jul 2026 16:03:58 -0700 Subject: [PATCH 5/9] Move client generation to separate openAPIClient process --- Makefile | 8 ++++---- foji.yaml | 4 ++++ foji/foji.yaml | 7 ++++++- foji/openapi/client.go.tpl | 23 +++++++++++------------ tests/auth/foji.yaml | 9 +++++++-- tests/auth/http_client_gen.go | 2 +- tests/csvresponse/foji.yaml | 9 +++++++-- tests/csvresponse/http_client_gen.go | 2 +- tests/example/foji.yaml | 9 +++++++-- tests/example/http_client_gen.go | 2 +- tests/go.mod | 2 ++ 11 files changed, 51 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index 82994e4..f5f103e 100644 --- a/Makefile +++ b/Makefile @@ -11,9 +11,9 @@ test: go test ./... test_generate: - go run main.go weld openAPI -c tests/csvresponse/foji.yaml - go run main.go weld openAPI -c tests/example/foji.yaml - go run main.go weld openAPI -c tests/auth/foji.yaml + go run main.go weld openAPI openAPIClient -c tests/csvresponse/foji.yaml + go run main.go weld openAPI openAPIClient -c tests/example/foji.yaml + go run main.go weld openAPI openAPIClient -c tests/auth/foji.yaml cd tests; go run tests_main.go test_gen: test_generate fmt @@ -38,4 +38,4 @@ tools: go install golang.org/x/tools/cmd/goimports@latest go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest -.PHONY: sqlRepo testSchemaList testStub testDumpConfig lint test test_gen test_generate cover tidy update updateAll install tools \ No newline at end of file +.PHONY: sqlRepo testSchemaList testStub testDumpConfig lint test test_gen test_generate cover tidy update updateAll install tools diff --git a/foji.yaml b/foji.yaml index c49859a..f3ff1f1 100644 --- a/foji.yaml +++ b/foji.yaml @@ -32,3 +32,7 @@ processes: params: Package: github.com/gofoji/foji/test Auth: github.com/gofoji/foji/test.User + openAPIClient: + params: + Package: github.com/gofoji/foji/test + Auth: github.com/gofoji/foji/test.User diff --git a/foji/foji.yaml b/foji/foji.yaml index 9145b57..50950f5 100644 --- a/foji/foji.yaml +++ b/foji/foji.yaml @@ -97,5 +97,10 @@ processes: 'models_gen.go': foji/openapi/model.go.tpl 'service_gen.go': foji/openapi/service.go.tpl 'handlers_gen.go': foji/openapi/handler.go.tpl - 'client_gen.go': foji/openapi/client.go.tpl '!cmd/serve/main.go': foji/openapi/main.go.tpl + openAPIClient: + format: go + resources: [ api ] + OpenAPIFile: + 'models_gen.go': foji/openapi/model.go.tpl + 'client_gen.go': foji/openapi/client.go.tpl diff --git a/foji/openapi/client.go.tpl b/foji/openapi/client.go.tpl index 51819a7..72d6a78 100644 --- a/foji/openapi/client.go.tpl +++ b/foji/openapi/client.go.tpl @@ -69,7 +69,7 @@ {{- $op := .RuntimeParams.op -}} {{- $package := .RuntimeParams.package -}} {{- $body := .GetRequestBody $op -}} - {{- if $.OpSecuritySchemes $op }} user *{{ $.CheckPackage ($.Params.GetWithDefault "ClientAuth" "") $package }},{{ end }} + {{- if $.OpSecuritySchemes $op }} user *{{ $.CheckPackage $.Params.Auth $package -}},{{ end }} {{- range $param := $.OpParams $path $op -}} {{- $name := print $op.OperationID " " $param.Value.Name -}} {{- if notEmpty $param.Ref }}{{ $name = trimPrefix "#/components/parameters/" $param.Ref }}{{ end -}} @@ -85,9 +85,8 @@ {{- end -}} {{- $package := $.PackageName }} -{{- $clientAuth := $.Params.GetWithDefault "ClientAuth" "" }} -// Code generated by foji {{ version }}, template: {{ templateFile }}; DO NOT EDIT. +// Code generated by foji, template: {{ templateFile }}; DO NOT EDIT. package {{ $package }} @@ -103,25 +102,25 @@ import ( "strings" "github.com/bir/iken/httputil" -{{- .CheckAllTypes $package $clientAuth -}} +{{- .CheckAllTypes $package ($.Params.GetWithDefault "Auth" "") -}} {{- range .GoImports }} "{{ . }}" {{- end }} ) {{ if .HasAuthentication -}} -{{ .ErrorIf (empty $clientAuth) "params.ClientAuth" -}} +{{ .ErrorIf (empty $.Params.Auth) "params.Auth" -}} type ( - ClientAuthenticator = httputil.ClientAuthenticateFunc[*{{ $.CheckPackage $clientAuth $package }}] - ClientTokenAuthenticator = httputil.ClientTokenAuthenticatorFunc[*{{ $.CheckPackage $clientAuth $package }}] - ClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*{{ $.CheckPackage $clientAuth $package }}] - ClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*{{ $.CheckPackage $clientAuth $package }}] - ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*{{ $.CheckPackage $clientAuth $package }}] + ClientAuthenticator = httputil.ClientAuthenticateFunc[*{{ $.CheckPackage $.Params.Auth $package }}] + ClientTokenAuthenticator = httputil.ClientTokenAuthenticatorFunc[*{{ $.CheckPackage $.Params.Auth $package }}] + ClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*{{ $.CheckPackage $.Params.Auth $package }}] + ClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*{{ $.CheckPackage $.Params.Auth $package }}] + ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*{{ $.CheckPackage $.Params.Auth $package }}] {{- if .HasComplexAuth }} - ClientSecurityGroup = httputil.ClientSecurityGroup[*{{ $.CheckPackage $clientAuth $package }}] - ClientSecurityGroups = httputil.ClientSecurityGroups[*{{ $.CheckPackage $clientAuth $package }}] + ClientSecurityGroup = httputil.ClientSecurityGroup[*{{ $.CheckPackage $.Params.Auth $package }}] + ClientSecurityGroups = httputil.ClientSecurityGroups[*{{ $.CheckPackage $.Params.Auth $package }}] {{- end }} ) diff --git a/tests/auth/foji.yaml b/tests/auth/foji.yaml index c2591dd..e2c0c7c 100644 --- a/tests/auth/foji.yaml +++ b/tests/auth/foji.yaml @@ -7,9 +7,14 @@ processes: params: Package: foji/tests/auth Auth: tests/example.ExampleAuth - ClientAuth: tests/example.ExampleAuth OpenAPIFile: 'tests/auth/http_handler_gen.go': foji/openapi/handler.go.tpl - 'tests/auth/http_client_gen.go': foji/openapi/client.go.tpl 'tests/auth/model_gen.go': foji/openapi/model.go.tpl 'tests/auth/service_gen.go': foji/openapi/service.go.tpl + openAPIClient: + params: + Package: foji/tests/auth + Auth: tests/example.ExampleAuth + OpenAPIFile: + 'tests/auth/model_gen.go': foji/openapi/model.go.tpl + 'tests/auth/http_client_gen.go': foji/openapi/client.go.tpl diff --git a/tests/auth/http_client_gen.go b/tests/auth/http_client_gen.go index 511490a..372c8ba 100644 --- a/tests/auth/http_client_gen.go +++ b/tests/auth/http_client_gen.go @@ -1,4 +1,4 @@ -// Code generated by foji (dev build), template: foji/openapi/client.go.tpl; DO NOT EDIT. +// Code generated by foji, template: foji/openapi/client.go.tpl; DO NOT EDIT. package auth diff --git a/tests/csvresponse/foji.yaml b/tests/csvresponse/foji.yaml index 0b01912..589ea48 100644 --- a/tests/csvresponse/foji.yaml +++ b/tests/csvresponse/foji.yaml @@ -7,8 +7,13 @@ processes: params: Package: foji/tests/csvresponse Auth: ExampleAuth - ClientAuth: ExampleAuth OpenAPIFile: 'tests/csvresponse/http_handler_gen.go': foji/openapi/handler.go.tpl - 'tests/csvresponse/http_client_gen.go': foji/openapi/client.go.tpl 'tests/csvresponse/model_gen.go': foji/openapi/model.go.tpl + openAPIClient: + params: + Package: foji/tests/csvresponse + Auth: ExampleAuth + OpenAPIFile: + 'tests/csvresponse/model_gen.go': foji/openapi/model.go.tpl + 'tests/csvresponse/http_client_gen.go': foji/openapi/client.go.tpl diff --git a/tests/csvresponse/http_client_gen.go b/tests/csvresponse/http_client_gen.go index cf80ba9..aac043a 100644 --- a/tests/csvresponse/http_client_gen.go +++ b/tests/csvresponse/http_client_gen.go @@ -1,4 +1,4 @@ -// Code generated by foji (dev build), template: foji/openapi/client.go.tpl; DO NOT EDIT. +// Code generated by foji, template: foji/openapi/client.go.tpl; DO NOT EDIT. package csvresponse diff --git a/tests/example/foji.yaml b/tests/example/foji.yaml index ad8778a..cc0ef72 100644 --- a/tests/example/foji.yaml +++ b/tests/example/foji.yaml @@ -7,8 +7,13 @@ processes: params: Package: foji/tests/example Auth: ExampleAuth - ClientAuth: ExampleAuth OpenAPIFile: tests/example/http_handler_gen.go: foji/openapi/handler.go.tpl - tests/example/http_client_gen.go: foji/openapi/client.go.tpl tests/example/model_gen.go: foji/openapi/model.go.tpl + openAPIClient: + params: + Package: foji/tests/example + Auth: ExampleAuth + OpenAPIFile: + tests/example/model_gen.go: foji/openapi/model.go.tpl + tests/example/http_client_gen.go: foji/openapi/client.go.tpl diff --git a/tests/example/http_client_gen.go b/tests/example/http_client_gen.go index 4e0edd9..751fc46 100644 --- a/tests/example/http_client_gen.go +++ b/tests/example/http_client_gen.go @@ -1,4 +1,4 @@ -// Code generated by foji (dev build), template: foji/openapi/client.go.tpl; DO NOT EDIT. +// Code generated by foji, template: foji/openapi/client.go.tpl; DO NOT EDIT. package example diff --git a/tests/go.mod b/tests/go.mod index 2093f3d..a1139cb 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -18,3 +18,5 @@ require ( golang.org/x/sys v0.41.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/bir/iken => /Users/david/LAVA/iken From a04b3bf12b446e8942f128e6756642aa378515d5 Mon Sep 17 00:00:00 2001 From: David Newgas Date: Mon, 13 Jul 2026 09:58:40 -0700 Subject: [PATCH 6/9] Remove a comment --- foji/openapi/client.go.tpl | 2 -- 1 file changed, 2 deletions(-) diff --git a/foji/openapi/client.go.tpl b/foji/openapi/client.go.tpl index 72d6a78..db8c939 100644 --- a/foji/openapi/client.go.tpl +++ b/foji/openapi/client.go.tpl @@ -16,8 +16,6 @@ {{- end -}} {{- end -}} -{{- /* clientAuth classifies a security scheme and emits the fragment for the requested mode: - param (NewClient argument), construct (Client field initializer) or apply (per-request call). */}} {{- define "clientAuth" -}} {{- $scheme := .RuntimeParams.scheme -}} {{- $mode := .RuntimeParams.mode -}} From a4c9ac523170835b8285ba8e7723cdad9b4906d5 Mon Sep 17 00:00:00 2001 From: David Newgas Date: Thu, 16 Jul 2026 09:33:53 -0700 Subject: [PATCH 7/9] Use iken 0.8.13 --- tests/go.mod | 4 +--- tests/go.sum | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/go.mod b/tests/go.mod index a1139cb..8cb22ea 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -3,7 +3,7 @@ module tests go 1.26.0 require ( - github.com/bir/iken v0.8.12 + github.com/bir/iken v0.8.13 github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.11.1 ) @@ -18,5 +18,3 @@ require ( golang.org/x/sys v0.41.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/bir/iken => /Users/david/LAVA/iken diff --git a/tests/go.sum b/tests/go.sum index 6a80f5e..8653908 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -1,5 +1,7 @@ github.com/bir/iken v0.8.12 h1:ROLRu5f8KIYSuKcSLzOrpbpaNLQHzzOUnYBihijvMzk= github.com/bir/iken v0.8.12/go.mod h1:F3gTwkXjHAJ0thzPYo2iOk+Ot35TOv4vmGq9JdEIlG0= +github.com/bir/iken v0.8.13 h1:KQ5IqszCnbk5ENTGxj9m8G1v1I1kqc94gJdY0mhotrM= +github.com/bir/iken v0.8.13/go.mod h1:F3gTwkXjHAJ0thzPYo2iOk+Ot35TOv4vmGq9JdEIlG0= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From 707c2e0229649010f3fed9f7541f079d5125203c Mon Sep 17 00:00:00 2001 From: David Newgas Date: Fri, 17 Jul 2026 13:40:17 -0700 Subject: [PATCH 8/9] Add typeprefix and interface to generated clients We may often want multiple clients in the same module, so this PR allows the generated client to have a prefix on the types. This commit also adds an interface, allowing easier mocking. --- foji/openapi/client.go.tpl | 53 +- foji/openapi/model.go.tpl | 56 +- output/openapi.go | 18 +- tests/auth/http_client_gen.go | 13 + tests/csvresponse/http_client_gen.go | 6 + tests/example/client_model_gen.go | 3484 ++++++++++++++++++++++++++ tests/example/foji.yaml | 3 +- tests/example/http_client_gen.go | 178 +- tests/tests_main.go | 10 +- 9 files changed, 3689 insertions(+), 132 deletions(-) create mode 100644 tests/example/client_model_gen.go diff --git a/foji/openapi/client.go.tpl b/foji/openapi/client.go.tpl index db8c939..12b18e0 100644 --- a/foji/openapi/client.go.tpl +++ b/foji/openapi/client.go.tpl @@ -31,12 +31,13 @@ {{- else if eq $v.In "cookie" -}}{{ $kind = "cookie" }} {{- else -}}{{ $kind = "header" }}{{- end -}} + {{- $prefix := $.Params.GetWithDefault "TypePrefix" "" -}} {{- if eq $mode "param" -}} - {{- if eq $kind "raw" -}}{{ $c }}Auth ClientAuthenticator - {{- else if eq $kind "basic" -}}{{ $c }}Auth ClientBasicAuthenticator - {{- else if eq $kind "cookie" -}}{{ $c }}Auth ClientCookieAuthenticator - {{- else if eq $kind "wrap" -}}{{ $c }}Auth ClientWrappingAuthenticator - {{- else -}}{{ $c }}Auth ClientTokenAuthenticator + {{- if eq $kind "raw" -}}{{ $c }}Auth {{ $prefix }}ClientAuthenticator + {{- else if eq $kind "basic" -}}{{ $c }}Auth {{ $prefix }}ClientBasicAuthenticator + {{- else if eq $kind "cookie" -}}{{ $c }}Auth {{ $prefix }}ClientCookieAuthenticator + {{- else if eq $kind "wrap" -}}{{ $c }}Auth {{ $prefix }}ClientWrappingAuthenticator + {{- else -}}{{ $c }}Auth {{ $prefix }}ClientTokenAuthenticator {{- end -}} {{- else if eq $mode "construct" }} {{- if eq $kind "raw" }} @@ -83,6 +84,7 @@ {{- end -}} {{- $package := $.PackageName }} +{{- $prefix := $.Params.GetWithDefault "TypePrefix" "" }} // Code generated by foji, template: {{ templateFile }}; DO NOT EDIT. @@ -110,38 +112,47 @@ import ( {{ .ErrorIf (empty $.Params.Auth) "params.Auth" -}} type ( - ClientAuthenticator = httputil.ClientAuthenticateFunc[*{{ $.CheckPackage $.Params.Auth $package }}] - ClientTokenAuthenticator = httputil.ClientTokenAuthenticatorFunc[*{{ $.CheckPackage $.Params.Auth $package }}] - ClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*{{ $.CheckPackage $.Params.Auth $package }}] - ClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*{{ $.CheckPackage $.Params.Auth $package }}] - ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*{{ $.CheckPackage $.Params.Auth $package }}] + {{ $prefix }}ClientAuthenticator = httputil.ClientAuthenticateFunc[*{{ $.CheckPackage $.Params.Auth $package }}] + {{ $prefix }}ClientTokenAuthenticator = httputil.ClientTokenAuthenticatorFunc[*{{ $.CheckPackage $.Params.Auth $package }}] + {{ $prefix }}ClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*{{ $.CheckPackage $.Params.Auth $package }}] + {{ $prefix }}ClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*{{ $.CheckPackage $.Params.Auth $package }}] + {{ $prefix }}ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*{{ $.CheckPackage $.Params.Auth $package }}] {{- if .HasComplexAuth }} - ClientSecurityGroup = httputil.ClientSecurityGroup[*{{ $.CheckPackage $.Params.Auth $package }}] - ClientSecurityGroups = httputil.ClientSecurityGroups[*{{ $.CheckPackage $.Params.Auth $package }}] + {{ $prefix }}ClientSecurityGroup = httputil.ClientSecurityGroup[*{{ $.CheckPackage $.Params.Auth $package }}] + {{ $prefix }}ClientSecurityGroups = httputil.ClientSecurityGroups[*{{ $.CheckPackage $.Params.Auth $package }}] {{- end }} ) {{ end -}} -type Client struct { +type {{ $prefix }}Methods interface { +{{- range $name, $path := .API.Paths.Map }} + {{- range $verb, $op := $path.Operations }} + {{ pascal $op.OperationID }}(ctx context.Context, + {{- template "clientMethodSignature" ($.WithParams "op" $op "package" $package "path" $path) }} + {{- end }} +{{- end }} +} + +type {{ $prefix }}Client struct { baseURL string httpClient *http.Client {{- range $security, $value := .API.Components.SecuritySchemes }} - {{ camel $security }}Auth ClientAuthenticator + {{ camel $security }}Auth {{ $prefix }}ClientAuthenticator {{- end }} {{- range $name, $path := .API.Paths.Map }} {{- range $verb, $op := $path.Operations }} {{- if not ($.IsSimpleAuth $op) }} - {{ camel $op.OperationID}}Security ClientSecurityGroups + {{ camel $op.OperationID}}Security {{ $prefix }}ClientSecurityGroups {{- end}} {{- end}} {{- end}} } -func NewClient(baseURL string, httpClient *http.Client -{{- range $security, $value := .API.Components.SecuritySchemes }}, {{ template "clientAuth" ($.WithParams "scheme" $security "mode" "param") }}{{- end }}) *Client { - c := &Client{ +func New{{ $prefix }}Client(baseURL string, httpClient *http.Client +{{- range $security, $value := .API.Components.SecuritySchemes }}, {{ template "clientAuth" ($.WithParams "scheme" $security "mode" "param") }}{{- end }}) *{{ $prefix }}Client { + c := &{{ $prefix }}Client{ baseURL: baseURL, httpClient: httpClient, {{- range $security, $value := .API.Components.SecuritySchemes }}{{ template "clientAuth" ($.WithParams "scheme" $security "mode" "construct") }}{{- end }} @@ -150,9 +161,9 @@ func NewClient(baseURL string, httpClient *http.Client {{- range $verb, $op := $path.Operations }} {{- if not ($.IsSimpleAuth $op) }} - c.{{ camel $op.OperationID}}Security = ClientSecurityGroups{ + c.{{ camel $op.OperationID}}Security = {{ $prefix }}ClientSecurityGroups{ {{- range $securityGroup := $.OpSecurity $op }} - ClientSecurityGroup{ + {{ $prefix }}ClientSecurityGroup{ {{- range $security, $scopes := $securityGroup -}} c.{{ camel $security }}Auth, {{- end -}} @@ -177,7 +188,7 @@ func NewClient(baseURL string, httpClient *http.Client {{ goDoc (pascal $op.OperationID) }} {{- goDoc $op.Summary }} {{- goDoc $op.Description }} -func (c *Client) {{ pascal $op.OperationID }}(ctx context.Context, +func (c *{{ $prefix }}Client) {{ pascal $op.OperationID }}(ctx context.Context, {{- template "clientMethodSignature" ($.WithParams "op" $op "package" $package "path" $path) }} { u := c.baseURL + "{{ $name }}" {{- $hasQuery := false }} diff --git a/foji/openapi/model.go.tpl b/foji/openapi/model.go.tpl index 96aa48a..073f599 100644 --- a/foji/openapi/model.go.tpl +++ b/foji/openapi/model.go.tpl @@ -195,6 +195,8 @@ func (e *{{ $enumType }}) Scan(src any) error { {{- if not ($.HasExtension $schema "x-go-type" )}} {{- $typeName := $.GetType $.PackageName $key $schema }} +{{- $rawName := pascal $key }} +{{- $declName := $.PrefixType $rawName }} // {{ $typeName}} {{- goDoc $schema.Value.Description }} // @@ -204,14 +206,14 @@ func (e *{{ $enumType }}) Scan(src any) error { {{- $label := .RuntimeParams.label }} {{- template "enum" ($.WithParams "name" $key "schema" $schema "description" (print $label " : " $key ))}} {{- else if and ($schema.Value.Type.Permits "object") (gt (len ($.SchemaProperties $schema)) 0) }} -type {{ pascal $key }} struct { +type {{ $declName }} struct { {{- range $field, $schemaProp := $.SchemaProperties $schema}} {{- $isRequired := $.IsRequiredProperty $field $schema -}} - {{- template "propertyDeclaration" ($.WithParams "key" $field "schema" $schemaProp "typeName" $typeName "isRequired" $isRequired)}} + {{- template "propertyDeclaration" ($.WithParams "key" $field "schema" $schemaProp "typeName" $rawName "isRequired" $isRequired)}} {{- end }} } {{- else }} -type {{ pascal $key }} {{ $.GetType $.PackageName (pascal (print $typeName " Item" )) $schema }} +type {{ $declName }} {{ $.GetType $.PackageName (pascal (print $rawName " Item" )) $schema }} {{- end }} {{- $hasValidation := $.HasValidation $schema -}} @@ -221,14 +223,14 @@ type {{ pascal $key }} {{ $.GetType $.PackageName (pascal (print $typeName " Ite {{- range $key, $schemaProp := $.SchemaProperties $schema }} {{- if not (empty $schemaProp.Value.Properties )}} {{- if empty $schemaProp.Ref -}} - {{- template "typeDeclaration" ($.WithParams "mediaType" "application/json" "key" (pascal (print $typeName " " $key)) "schema" $schemaProp "label" (print $typeName " inline " $key))}} + {{- template "typeDeclaration" ($.WithParams "mediaType" "application/json" "key" (pascal (print $rawName " " $key)) "schema" $schemaProp "label" (print $typeName " inline " $key))}} {{- end -}} {{- else if $schemaProp.Value.Type.Is "array"}} {{- if empty $schemaProp.Value.Items.Ref -}} {{- $isEnumItem := $.IsDefaultEnum $key $schemaProp.Value.Items }} {{- $hasProperties := not (empty ($.SchemaProperties $schemaProp.Value.Items )) }} {{- if or $isEnumItem $hasProperties}} - {{- template "typeDeclaration" ($.WithParams "mediaType" "application/json" "key" (pascal (print $typeName " " $key)) "schema" $schemaProp.Value.Items "label" (print $typeName " inline item " $key))}} + {{- template "typeDeclaration" ($.WithParams "mediaType" "application/json" "key" (pascal (print $rawName " " $key)) "schema" $schemaProp.Value.Items "label" (print $typeName " inline item " $key))}} {{- end }} {{- end }} {{- end }} @@ -239,7 +241,7 @@ type {{ pascal $key }} {{ $.GetType $.PackageName (pascal (print $typeName " Ite {{- $isEnumItem := $.IsDefaultEnum $key $schema.Value.Items }} {{- $hasProperties := not (empty ($.SchemaProperties $schema.Value.Items )) }} {{- if or $isEnumItem $hasProperties}} - {{- template "typeDeclaration" ($.WithParams "mediaType" "application/json" "key" (pascal (print $typeName " Item" )) "schema" $schema.Value.Items "label" (print $typeName " inline item " $key))}} + {{- template "typeDeclaration" ($.WithParams "mediaType" "application/json" "key" (pascal (print $rawName " Item" )) "schema" $schema.Value.Items "label" (print $typeName " inline item " $key))}} {{- end }} {{- end }} {{- end }} @@ -247,29 +249,29 @@ type {{ pascal $key }} {{ $.GetType $.PackageName (pascal (print $typeName " Ite {{- /* Regex Validation Patterns */ -}} {{- range $key, $schemaProp := $.SchemaProperties $schema }} {{- if and (notEmpty $schemaProp.Value.Pattern) (empty $schemaProp.Ref) }} -var {{ camel $typeName }}{{ pascal $key }}Pattern = regexp.MustCompile(`{{ $schemaProp.Value.Pattern }}`) +var {{ camel $declName }}{{ pascal $key }}Pattern = regexp.MustCompile(`{{ $schemaProp.Value.Pattern }}`) {{- end}} {{- end }} {{- if and $hasValidation (notEmpty $schema.Value.Pattern) (empty $schema.Ref) }} -var {{ camel $key }}Pattern = regexp.MustCompile(`{{ $schema.Value.Pattern }}`) +var {{ camel $declName }}Pattern = regexp.MustCompile(`{{ $schema.Value.Pattern }}`) {{ end }} {{- /* Enums */}} {{- range $key, $schemaEnum := $.SchemaEnums $schema }} - {{- template "enum" ($.WithParams "name" (print $typeName " " $key) "schema" $schemaEnum "description" (print $label " : " $key ))}} + {{- template "enum" ($.WithParams "name" (print $rawName " " $key) "schema" $schemaEnum "description" (print $label " : " $key ))}} {{- end -}} {{if eq $mediaType "application/json" }} {{- if or $hasValidation $hasRequired (.SchemaPropertiesHaveDefaults $schema)}} -func (p *{{ pascal $key }}) UnmarshalJSON(b []byte) error { +func (p *{{ $declName }}) UnmarshalJSON(b []byte) error { var err error {{- if or $hasRequired (.SchemaPropertiesHaveDefaults $schema) }} var requiredCheck map[string]any err = json.Unmarshal(b, &requiredCheck) if err != nil { - return validation.Error{err.Error(), fmt.Errorf("{{ pascal $key }}.UnmarshalJSON Required: `%v`: %w", string(b), err)} + return validation.Error{err.Error(), fmt.Errorf("{{ $declName }}.UnmarshalJSON Required: `%v`: %w", string(b), err)} } var validationErrors validation.Errors @@ -284,15 +286,15 @@ func (p *{{ pascal $key }}) UnmarshalJSON(b []byte) error { } {{ end }} - type {{ pascal $key }}JSON {{ pascal $key }} - var parseObject {{ pascal $key }}JSON + type {{ $declName }}JSON {{ $declName }} + var parseObject {{ $declName }}JSON err = json.Unmarshal(b, &parseObject) if err != nil { - return validation.Error{err.Error(), fmt.Errorf("{{ pascal $key }}.UnmarshalJSON: `%v`: %w", string(b), err)} + return validation.Error{err.Error(), fmt.Errorf("{{ $declName }}.UnmarshalJSON: `%v`: %w", string(b), err)} } - v := {{ pascal $key }}(parseObject) + v := {{ $declName }}(parseObject) {{ range $field, $schemaProp := $.SchemaProperties $schema}} {{ $typeName := (print $key " " $field) -}} @@ -338,16 +340,16 @@ func (p *{{ pascal $key }}) UnmarshalJSON(b []byte) error { } {{ if $hasValidation}} -func (p {{ pascal $key }}) MarshalJSON() ([]byte, error) { +func (p {{ $declName }}) MarshalJSON() ([]byte, error) { err := p.Validate() if err != nil { return nil, err } - type unvalidated {{ pascal $key }} // Skips the validation check + type unvalidated {{ $declName }} // Skips the validation check b, err := json.Marshal(unvalidated(p)) if err != nil { - return nil, fmt.Errorf("{{ pascal $key }}.Marshal: `%+v`: %w", p, err) + return nil, fmt.Errorf("{{ $declName }}.Marshal: `%+v`: %w", p, err) } return b, nil @@ -359,11 +361,11 @@ func (p {{ pascal $key }}) MarshalJSON() ([]byte, error) { {{if or (eq $mediaType "multipart/form-data") (eq $mediaType "application/x-www-form-urlencoded")}} -func ParseForm{{ pascal $key }}(r *http.Request) ({{ pascal $key }}, error) { +func ParseForm{{ $declName }}(r *http.Request) ({{ $declName }}, error) { var ( parseErrors validation.Errors err error - v {{ pascal $key }} + v {{ $declName }} ) {{ range $field, $schemaProp := $.SchemaProperties $schema }} @@ -433,13 +435,13 @@ func ParseForm{{ pascal $key }}(r *http.Request) ({{ pascal $key }}, error) { {{ end }} if parseErrors != nil { - return {{ pascal $key }}{}, parseErrors.GetErr() + return {{ $declName }}{}, parseErrors.GetErr() } {{ if $hasValidation}} err = v.Validate() if err != nil { - return {{ pascal $key }}{}, err + return {{ $declName }}{}, err } {{ end }} @@ -451,7 +453,7 @@ func ParseForm{{ pascal $key }}(r *http.Request) ({{ pascal $key }}, error) { {{- if $hasValidation }} {{ $properties := $.SchemaProperties $schema }} {{- if not (empty $properties )}} -func (p {{ pascal $key }}) Validate() error { +func (p {{ $declName }}) Validate() error { var err validation.Errors {{ range $fieldName, $schemaProp := $properties }} {{- if $.HasValidation $schemaProp }} @@ -466,21 +468,21 @@ func (p {{ pascal $key }}) Validate() error { {{- $isRequired := $.IsRequiredProperty $fieldName $schema -}} {{- $isPointer := and (not $isRequired) ($schemaProp.Value.Nullable) }} -func (p {{ pascal $key }}) Validate{{ pascal $fieldName }}(err *validation.Errors) { +func (p {{ $declName }}) Validate{{ pascal $fieldName }}(err *validation.Errors) { {{- if $isPointer }} if p.{{ pascal $fieldName }} == nil { return } {{ end -}} - {{- template "validateField" ($.WithParams "fieldName" $fieldName "schema" $schemaProp "typeName" $key "isPointer" $isPointer ) -}} + {{- template "validateField" ($.WithParams "fieldName" $fieldName "schema" $schemaProp "typeName" $declName "isPointer" $isPointer ) -}} } {{- end }} {{- end }} {{ else -}} -func (p {{ pascal $key }}) Validate() error { +func (p {{ $declName }}) Validate() error { var err validation.Errors - {{- template "validateField" ($.WithParams "fieldName" "" "schema" $schema "typeName" $key "isPointer" false )}} + {{- template "validateField" ($.WithParams "fieldName" "" "schema" $schema "typeName" $declName "isPointer" false )}} return err.GetErr() } diff --git a/output/openapi.go b/output/openapi.go index 3bc083a..433d062 100644 --- a/output/openapi.go +++ b/output/openapi.go @@ -95,7 +95,19 @@ func (o *OpenAPIFileContext) GetTypeName(pkg string, s *openapi3.SchemaRef) stri return o.CheckPackage(t, pkg) } - return o.CheckPackage(ref, pkg) + return o.CheckPackage(o.PrefixType(ref), pkg) +} + +// PrefixType applies the TypePrefix param (if any) to a type name, which may be package-qualified. +func (o *OpenAPIFileContext) PrefixType(name string) string { + prefix := o.Params.GetWithDefault("TypePrefix", "") + if prefix == "" { + return name + } + + pos := strings.LastIndex(name, ".") + 1 + + return name[:pos] + prefix + name[pos:] } func (o *OpenAPIFileContext) TypeOnly(name string) string { @@ -193,7 +205,7 @@ func (o *OpenAPIFileContext) GetType(currentPackage, name string, s *openapi3.Sc return "any" } - name = o.PackageName() + "." + kace.Pascal(name) + name = o.PrefixType(o.PackageName() + "." + kace.Pascal(name)) return o.CheckPackage(name, currentPackage) } @@ -211,7 +223,7 @@ func (o *OpenAPIFileContext) GetType(currentPackage, name string, s *openapi3.Sc func (o *OpenAPIFileContext) EnumName(name string) string { // TODO: Support override via template - return o.PackageName() + "." + kace.Pascal(name) + return o.PrefixType(o.PackageName() + "." + kace.Pascal(name)) } func (o *OpenAPIFileContext) EnumNew(name string) string { diff --git a/tests/auth/http_client_gen.go b/tests/auth/http_client_gen.go index 372c8ba..995358f 100644 --- a/tests/auth/http_client_gen.go +++ b/tests/auth/http_client_gen.go @@ -22,6 +22,19 @@ type ( ClientSecurityGroups = httputil.ClientSecurityGroups[*example.ExampleAuth] ) +type Methods interface { + ListAdminUsers(ctx context.Context, user *example.ExampleAuth) ([]User, error) + QueryDataWithApiKey(ctx context.Context, user *example.ExampleAuth, query *string) error + ListDocuments(ctx context.Context, user *example.ExampleAuth) error + CreateDocument(ctx context.Context, user *example.ExampleAuth) error + Overview(ctx context.Context, user *example.ExampleAuth) error + GetDetailedProfile(ctx context.Context, user *example.ExampleAuth) error + GetProtectedResource(ctx context.Context, user *example.ExampleAuth) error + GetPublicStatus(ctx context.Context) (*GetPublicStatusResponse, error) + GetCurrentUser(ctx context.Context, user *example.ExampleAuth) (*User, error) + GetUserProfile(ctx context.Context, user *example.ExampleAuth) (*User, error) +} + type Client struct { baseURL string httpClient *http.Client diff --git a/tests/csvresponse/http_client_gen.go b/tests/csvresponse/http_client_gen.go index aac043a..a90ff98 100644 --- a/tests/csvresponse/http_client_gen.go +++ b/tests/csvresponse/http_client_gen.go @@ -19,6 +19,12 @@ type ( ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*ExampleAuth] ) +type Methods interface { + GetByteCsv(ctx context.Context) ([]byte, error) + GetReaderCsv(ctx context.Context) (io.Reader, error) + GetStringCsv(ctx context.Context) (string, error) +} + type Client struct { baseURL string httpClient *http.Client diff --git a/tests/example/client_model_gen.go b/tests/example/client_model_gen.go new file mode 100644 index 0000000..f1cf2b6 --- /dev/null +++ b/tests/example/client_model_gen.go @@ -0,0 +1,3484 @@ +// Code generated by foji, template: foji/openapi/model.go.tpl; DO NOT EDIT. + +package example + +import ( + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + "math" + "net/http" + "regexp" + "time" + + "github.com/bir/iken/forms" + "github.com/bir/iken/validation" + "github.com/google/uuid" +) + +var ErrExampleMissingRequiredField = errors.New("missing required field") + +// Component Schemas + +// ExampleBar +// +// OpenAPI Component Schema: Bar +type ExampleBar struct { + Bars string `json:"bars,omitempty,omitzero"` +} + +var exampleBarBarsPattern = regexp.MustCompile(`(b1|b2)`) + +func (p *ExampleBar) UnmarshalJSON(b []byte) error { + var err error + + type ExampleBarJSON ExampleBar + var parseObject ExampleBarJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleBar.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleBar(parseObject) + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExampleBar) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExampleBar // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExampleBar.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExampleBar) Validate() error { + var err validation.Errors + + p.ValidateBars(&err) + + return err.GetErr() +} + +func (p ExampleBar) ValidateBars(err *validation.Errors) { + if len(p.Bars) < 2 { + _ = err.Add("bars", "length must be >= 2") + } + + if p.Bars != "" && !exampleBarBarsPattern.MatchString(string(p.Bars)) { + _ = err.Add("bars", `must match "(b1|b2)"`) + } +} + +// ExampleBuzz +// +// OpenAPI Component Schema: Buzz +type ExampleBuzz struct { + Buzzes string `json:"buzzes,omitempty,omitzero"` +} + +// ExampleDefaultWithoutRequired +// +// OpenAPI Component Schema: DefaultWithoutRequired +type ExampleDefaultWithoutRequired struct { + F1 string `json:"f1,omitempty,omitzero"` +} + +func (p *ExampleDefaultWithoutRequired) UnmarshalJSON(b []byte) error { + var err error + var requiredCheck map[string]any + + err = json.Unmarshal(b, &requiredCheck) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleDefaultWithoutRequired.UnmarshalJSON Required: `%v`: %w", string(b), err)} + } + + var validationErrors validation.Errors + + if validationErrors != nil { + return validationErrors.GetErr() + } + + type ExampleDefaultWithoutRequiredJSON ExampleDefaultWithoutRequired + var parseObject ExampleDefaultWithoutRequiredJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleDefaultWithoutRequired.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleDefaultWithoutRequired(parseObject) + + if _, ok := requiredCheck["f1"]; !ok { + v.F1 = "surprise!" + } + + *p = v + + return nil +} + +// ExampleExample +// +// OpenAPI Component Schema: Example +type ExampleExample struct { + ID uuid.UUID `json:"id"` + IdMaybe *uuid.UUID `json:"idMaybe,omitempty,omitzero"` + Name string `json:"name,omitempty,omitzero"` + PlayerAlways ExamplePlayerAlways `json:"playerAlways,omitempty,omitzero"` + PlayerID uuid.UUID `json:"playerId,omitempty,omitzero"` + PlayerMaybe *ExamplePlayerMaybe `json:"playerMaybe,omitempty,omitzero"` +} + +func (p *ExampleExample) UnmarshalJSON(b []byte) error { + var err error + var requiredCheck map[string]any + + err = json.Unmarshal(b, &requiredCheck) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleExample.UnmarshalJSON Required: `%v`: %w", string(b), err)} + } + + var validationErrors validation.Errors + + if _, ok := requiredCheck["id"]; !ok { + validationErrors.Add("id", ErrExampleMissingRequiredField) + } + + if validationErrors != nil { + return validationErrors.GetErr() + } + + type ExampleExampleJSON ExampleExample + var parseObject ExampleExampleJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleExample.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleExample(parseObject) + + *p = v + + return nil +} + +// ExampleExamples +// +// OpenAPI Component Schema: Examples +type ExampleExamples struct { + List []ExampleExample `json:"list"` +} + +func (p *ExampleExamples) UnmarshalJSON(b []byte) error { + var err error + var requiredCheck map[string]any + + err = json.Unmarshal(b, &requiredCheck) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleExamples.UnmarshalJSON Required: `%v`: %w", string(b), err)} + } + + var validationErrors validation.Errors + + if _, ok := requiredCheck["list"]; !ok { + validationErrors.Add("list", ErrExampleMissingRequiredField) + } + + if validationErrors != nil { + return validationErrors.GetErr() + } + + type ExampleExamplesJSON ExampleExamples + var parseObject ExampleExamplesJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleExamples.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleExamples(parseObject) + + *p = v + + return nil +} + +// ExampleFoo +// +// OpenAPI Component Schema: Foo +type ExampleFoo struct { + Foos ExampleFooString `json:"foos,omitempty,omitzero"` +} + +func (p *ExampleFoo) UnmarshalJSON(b []byte) error { + var err error + + type ExampleFooJSON ExampleFoo + var parseObject ExampleFooJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleFoo.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleFoo(parseObject) + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExampleFoo) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExampleFoo // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExampleFoo.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExampleFoo) Validate() error { + var err validation.Errors + + p.ValidateFoos(&err) + + return err.GetErr() +} + +func (p ExampleFoo) ValidateFoos(err *validation.Errors) { + if subErr := p.Foos.Validate(); subErr != nil { + _ = err.Add("foos", subErr) + } +} + +// ExampleFooBar +// +// OpenAPI Component Schema: FooBar +type ExampleFooBar struct { + A string `json:"a"` + B ExampleSeason `json:"b,omitempty,omitzero"` + Bars string `json:"bars,omitempty,omitzero"` + C ExampleIntValue `json:"c,omitempty"` + Foos ExampleFooString `json:"foos,omitempty,omitzero"` +} + +var exampleFooBarBarsPattern = regexp.MustCompile(`(b1|b2)`) + +func (p *ExampleFooBar) UnmarshalJSON(b []byte) error { + var err error + var requiredCheck map[string]any + + err = json.Unmarshal(b, &requiredCheck) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleFooBar.UnmarshalJSON Required: `%v`: %w", string(b), err)} + } + + var validationErrors validation.Errors + + if _, ok := requiredCheck["a"]; !ok { + validationErrors.Add("a", ErrExampleMissingRequiredField) + } + + if validationErrors != nil { + return validationErrors.GetErr() + } + + type ExampleFooBarJSON ExampleFooBar + var parseObject ExampleFooBarJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleFooBar.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleFooBar(parseObject) + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExampleFooBar) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExampleFooBar // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExampleFooBar.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExampleFooBar) Validate() error { + var err validation.Errors + + p.ValidateA(&err) + p.ValidateBars(&err) + p.ValidateC(&err) + p.ValidateFoos(&err) + + return err.GetErr() +} + +func (p ExampleFooBar) ValidateA(err *validation.Errors) { + if len(p.A) < 2 { + _ = err.Add("a", "length must be >= 2") + } +} + +func (p ExampleFooBar) ValidateBars(err *validation.Errors) { + if len(p.Bars) < 2 { + _ = err.Add("bars", "length must be >= 2") + } + + if p.Bars != "" && !exampleFooBarBarsPattern.MatchString(string(p.Bars)) { + _ = err.Add("bars", `must match "(b1|b2)"`) + } +} + +func (p ExampleFooBar) ValidateC(err *validation.Errors) { + if subErr := p.C.Validate(); subErr != nil { + _ = err.Add("c", subErr) + } +} + +func (p ExampleFooBar) ValidateFoos(err *validation.Errors) { + if subErr := p.Foos.Validate(); subErr != nil { + _ = err.Add("foos", subErr) + } +} + +// ExampleFooBarBuzz +// +// OpenAPI Component Schema: FooBarBuzz +type ExampleFooBarBuzz struct { + A string `json:"a,omitempty,omitzero"` + B ExampleSeason `json:"b,omitempty,omitzero"` + Bars string `json:"bars,omitempty,omitzero"` + Buzzes string `json:"buzzes,omitempty,omitzero"` + C ExampleIntValue `json:"c,omitempty"` + Foos ExampleFooString `json:"foos,omitempty,omitzero"` + X bool `json:"x,omitempty"` +} + +var exampleFooBarBuzzBarsPattern = regexp.MustCompile(`(b1|b2)`) + +func (p *ExampleFooBarBuzz) UnmarshalJSON(b []byte) error { + var err error + + type ExampleFooBarBuzzJSON ExampleFooBarBuzz + var parseObject ExampleFooBarBuzzJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleFooBarBuzz.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleFooBarBuzz(parseObject) + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExampleFooBarBuzz) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExampleFooBarBuzz // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExampleFooBarBuzz.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExampleFooBarBuzz) Validate() error { + var err validation.Errors + + p.ValidateA(&err) + p.ValidateBars(&err) + p.ValidateC(&err) + p.ValidateFoos(&err) + + return err.GetErr() +} + +func (p ExampleFooBarBuzz) ValidateA(err *validation.Errors) { + if len(p.A) < 2 { + _ = err.Add("a", "length must be >= 2") + } +} + +func (p ExampleFooBarBuzz) ValidateBars(err *validation.Errors) { + if len(p.Bars) < 2 { + _ = err.Add("bars", "length must be >= 2") + } + + if p.Bars != "" && !exampleFooBarBuzzBarsPattern.MatchString(string(p.Bars)) { + _ = err.Add("bars", `must match "(b1|b2)"`) + } +} + +func (p ExampleFooBarBuzz) ValidateC(err *validation.Errors) { + if subErr := p.C.Validate(); subErr != nil { + _ = err.Add("c", subErr) + } +} + +func (p ExampleFooBarBuzz) ValidateFoos(err *validation.Errors) { + if subErr := p.Foos.Validate(); subErr != nil { + _ = err.Add("foos", subErr) + } +} + +// string +// +// OpenAPI Component Schema: FooString +type ExampleFooString string + +var exampleFooStringPattern = regexp.MustCompile(`(f1|f2)`) + +func (p *ExampleFooString) UnmarshalJSON(b []byte) error { + var err error + + type ExampleFooStringJSON ExampleFooString + var parseObject ExampleFooStringJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleFooString.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleFooString(parseObject) + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExampleFooString) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExampleFooString // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExampleFooString.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExampleFooString) Validate() error { + var err validation.Errors + + if p != "" && !exampleFooStringPattern.MatchString(string(p)) { + _ = err.Add("", `must match "(f1|f2)"`) + } + + return err.GetErr() +} + +// int32 +// +// OpenAPI Component Schema: IntValue +type ExampleIntValue int32 + +func (p *ExampleIntValue) UnmarshalJSON(b []byte) error { + var err error + + type ExampleIntValueJSON ExampleIntValue + var parseObject ExampleIntValueJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleIntValue.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleIntValue(parseObject) + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExampleIntValue) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExampleIntValue // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExampleIntValue.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExampleIntValue) Validate() error { + var err validation.Errors + + if p < 2 { + _ = err.Add("", "must be >= 2") + } + + if p > 10 { + _ = err.Add("", "must be <= 10") + } + + if p%2 != 0 { + _ = err.Add("", "must be multiple of 2") + } + + return err.GetErr() +} + +// any +// Named object +// +// OpenAPI Component Schema: NamedObject +type ExampleNamedObject any + +// ExampleNotRequiredWithValidation +// +// OpenAPI Component Schema: NotRequiredWithValidation +type ExampleNotRequiredWithValidation struct { + F1 []string `json:"f1,omitempty"` +} + +func (p *ExampleNotRequiredWithValidation) UnmarshalJSON(b []byte) error { + var err error + + type ExampleNotRequiredWithValidationJSON ExampleNotRequiredWithValidation + var parseObject ExampleNotRequiredWithValidationJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleNotRequiredWithValidation.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleNotRequiredWithValidation(parseObject) + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExampleNotRequiredWithValidation) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExampleNotRequiredWithValidation // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExampleNotRequiredWithValidation.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExampleNotRequiredWithValidation) Validate() error { + var err validation.Errors + + p.ValidateF1(&err) + + return err.GetErr() +} + +func (p ExampleNotRequiredWithValidation) ValidateF1(err *validation.Errors) { + if len(p.F1) < 2 { + _ = err.Add("f1", "length must be >= 2") + } +} + +// ExamplePatterns +// +// OpenAPI Component Schema: Patterns +type ExamplePatterns struct { + ID uuid.UUID `json:"id"` + IdMaybe uuid.UUID `json:"idMaybe,omitempty,omitzero"` + State *string `json:"state,omitempty,omitzero"` + State2 string `json:"state2,omitempty,omitzero"` + State3 *string `json:"state3,omitempty,omitzero"` + StateAlways string `json:"stateAlways"` + SubInt ExampleIntValue `json:"subInt,omitempty"` + SubObject ExampleSubPattern `json:"subObject"` + SubObjectMaybe *ExampleSubPattern `json:"subObjectMaybe,omitempty,omitzero"` + SubState ExampleState `json:"subState"` + SubStateMaybe *ExampleState `json:"subStateMaybe,omitempty,omitzero"` + SubValue ExamplePatternsSubValue `json:"subValue,omitempty,omitzero"` + TimeStamp time.Time `json:"timeStamp"` + TimeStampMaybe time.Time `json:"timeStampMaybe,omitempty,omitzero"` + Value int32 `json:"value,omitempty"` + ValueF float32 `json:"valueF,omitempty"` + ValueFmaybe *float32 `json:"valueFMaybe,omitempty"` + ValueMaybe *int32 `json:"valueMaybe,omitempty"` + Values []string `json:"values,omitempty"` + ValuesMaybe *[]string `json:"valuesMaybe,omitempty"` +} + +// ExamplePatternsSubValue +// +// OpenAPI ExamplePatterns inline subValue: PatternsSubValue +type ExamplePatternsSubValue struct { + Num int32 `json:"num,omitempty"` +} + +func (p *ExamplePatternsSubValue) UnmarshalJSON(b []byte) error { + var err error + + type ExamplePatternsSubValueJSON ExamplePatternsSubValue + var parseObject ExamplePatternsSubValueJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExamplePatternsSubValue.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExamplePatternsSubValue(parseObject) + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExamplePatternsSubValue) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExamplePatternsSubValue // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExamplePatternsSubValue.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExamplePatternsSubValue) Validate() error { + var err validation.Errors + + p.ValidateNum(&err) + + return err.GetErr() +} + +func (p ExamplePatternsSubValue) ValidateNum(err *validation.Errors) { + if p.Num < 2 { + _ = err.Add("num", "must be >= 2") + } +} + +var ( + examplePatternsStatePattern = regexp.MustCompile(`(enabled|disabled)`) + examplePatternsState2Pattern = regexp.MustCompile(`(enabled|disabled)`) + examplePatternsStateAlwaysPattern = regexp.MustCompile(`(enabled|disabled)`) +) + +func (p *ExamplePatterns) UnmarshalJSON(b []byte) error { + var err error + var requiredCheck map[string]any + + err = json.Unmarshal(b, &requiredCheck) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExamplePatterns.UnmarshalJSON Required: `%v`: %w", string(b), err)} + } + + var validationErrors validation.Errors + + if _, ok := requiredCheck["id"]; !ok { + validationErrors.Add("id", ErrExampleMissingRequiredField) + } + + if _, ok := requiredCheck["stateAlways"]; !ok { + validationErrors.Add("stateAlways", ErrExampleMissingRequiredField) + } + + if _, ok := requiredCheck["subObject"]; !ok { + validationErrors.Add("subObject", ErrExampleMissingRequiredField) + } + + if _, ok := requiredCheck["subState"]; !ok { + validationErrors.Add("subState", ErrExampleMissingRequiredField) + } + + if _, ok := requiredCheck["timeStamp"]; !ok { + validationErrors.Add("timeStamp", ErrExampleMissingRequiredField) + } + + if validationErrors != nil { + return validationErrors.GetErr() + } + + type ExamplePatternsJSON ExamplePatterns + var parseObject ExamplePatternsJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExamplePatterns.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExamplePatterns(parseObject) + + if _, ok := requiredCheck["state3"]; !ok { + var defaultVal string = "completed" + v.State3 = &defaultVal + } + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExamplePatterns) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExamplePatterns // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExamplePatterns.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExamplePatterns) Validate() error { + var err validation.Errors + + p.ValidateState(&err) + p.ValidateState2(&err) + p.ValidateStateAlways(&err) + p.ValidateSubInt(&err) + p.ValidateSubObject(&err) + p.ValidateSubObjectMaybe(&err) + p.ValidateSubState(&err) + p.ValidateSubStateMaybe(&err) + p.ValidateSubValue(&err) + p.ValidateValue(&err) + p.ValidateValueF(&err) + p.ValidateValueFmaybe(&err) + p.ValidateValueMaybe(&err) + p.ValidateValues(&err) + p.ValidateValuesMaybe(&err) + + return err.GetErr() +} + +func (p ExamplePatterns) ValidateState(err *validation.Errors) { + if p.State == nil { + return + } + + if len(*p.State) < 2 { + _ = err.Add("state", "length must be >= 2") + } + + if len(*p.State) > 10 { + _ = err.Add("state", "length must be <= 10") + } + + if *p.State != "" && !examplePatternsStatePattern.MatchString(string(*p.State)) { + _ = err.Add("state", `must match "(enabled|disabled)"`) + } +} + +func (p ExamplePatterns) ValidateState2(err *validation.Errors) { + if p.State2 != "" && !examplePatternsState2Pattern.MatchString(string(p.State2)) { + _ = err.Add("state2", `must match "(enabled|disabled)"`) + } +} + +func (p ExamplePatterns) ValidateStateAlways(err *validation.Errors) { + if p.StateAlways != "" && !examplePatternsStateAlwaysPattern.MatchString(string(p.StateAlways)) { + _ = err.Add("stateAlways", `must match "(enabled|disabled)"`) + } +} + +func (p ExamplePatterns) ValidateSubInt(err *validation.Errors) { + if subErr := p.SubInt.Validate(); subErr != nil { + _ = err.Add("subInt", subErr) + } +} + +func (p ExamplePatterns) ValidateSubObject(err *validation.Errors) { + if subErr := p.SubObject.Validate(); subErr != nil { + _ = err.Add("subObject", subErr) + } +} + +func (p ExamplePatterns) ValidateSubObjectMaybe(err *validation.Errors) { + if p.SubObjectMaybe == nil { + return + } + + if p.SubObjectMaybe != nil { + if subErr := p.SubObjectMaybe.Validate(); subErr != nil { + _ = err.Add("subObjectMaybe", subErr) + } + } +} + +func (p ExamplePatterns) ValidateSubState(err *validation.Errors) { + if subErr := p.SubState.Validate(); subErr != nil { + _ = err.Add("subState", subErr) + } +} + +func (p ExamplePatterns) ValidateSubStateMaybe(err *validation.Errors) { + if p.SubStateMaybe == nil { + return + } + + if p.SubStateMaybe != nil { + if subErr := p.SubStateMaybe.Validate(); subErr != nil { + _ = err.Add("subStateMaybe", subErr) + } + } +} + +func (p ExamplePatterns) ValidateSubValue(err *validation.Errors) { + if subErr := p.SubValue.Validate(); subErr != nil { + _ = err.Add("subValue", subErr) + } +} + +func (p ExamplePatterns) ValidateValue(err *validation.Errors) { + if p.Value < 2 { + _ = err.Add("value", "must be >= 2") + } + + if p.Value > 10 { + _ = err.Add("value", "must be <= 10") + } + + if p.Value%2 != 0 { + _ = err.Add("value", "must be multiple of 2") + } +} + +func (p ExamplePatterns) ValidateValueF(err *validation.Errors) { + if p.ValueF < 2 { + _ = err.Add("valueF", "must be >= 2") + } + + if p.ValueF > 10 { + _ = err.Add("valueF", "must be <= 10") + } + + if math.Mod(float64(p.ValueF), 2) != 0 { + _ = err.Add("valueF", "must be multiple of 2") + } +} + +func (p ExamplePatterns) ValidateValueFmaybe(err *validation.Errors) { + if p.ValueFmaybe == nil { + return + } + + if *p.ValueFmaybe < 2 { + _ = err.Add("valueFMaybe", "must be >= 2") + } + + if *p.ValueFmaybe > 10 { + _ = err.Add("valueFMaybe", "must be <= 10") + } + + if math.Mod(float64(*p.ValueFmaybe), 2) != 0 { + _ = err.Add("valueFMaybe", "must be multiple of 2") + } +} + +func (p ExamplePatterns) ValidateValueMaybe(err *validation.Errors) { + if p.ValueMaybe == nil { + return + } + + if *p.ValueMaybe < 2 { + _ = err.Add("valueMaybe", "must be >= 2") + } + + if *p.ValueMaybe > 10 { + _ = err.Add("valueMaybe", "must be <= 10") + } + + if *p.ValueMaybe%2 != 0 { + _ = err.Add("valueMaybe", "must be multiple of 2") + } +} + +func (p ExamplePatterns) ValidateValues(err *validation.Errors) { + if len(p.Values) < 2 { + _ = err.Add("values", "length must be >= 2") + } + + if len(p.Values) > 10 { + _ = err.Add("values", "length must be <= 10") + } +} + +func (p ExamplePatterns) ValidateValuesMaybe(err *validation.Errors) { + if p.ValuesMaybe == nil { + return + } + + if len(*p.ValuesMaybe) < 2 { + _ = err.Add("valuesMaybe", "length must be >= 2") + } + + if len(*p.ValuesMaybe) > 10 { + _ = err.Add("valuesMaybe", "length must be <= 10") + } +} + +// ExamplePlayerAlways +// +// OpenAPI Component Schema: PlayerAlways +type ExamplePlayerAlways struct { + ID uuid.UUID `json:"id"` +} + +func (p *ExamplePlayerAlways) UnmarshalJSON(b []byte) error { + var err error + var requiredCheck map[string]any + + err = json.Unmarshal(b, &requiredCheck) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExamplePlayerAlways.UnmarshalJSON Required: `%v`: %w", string(b), err)} + } + + var validationErrors validation.Errors + + if _, ok := requiredCheck["id"]; !ok { + validationErrors.Add("id", ErrExampleMissingRequiredField) + } + + if validationErrors != nil { + return validationErrors.GetErr() + } + + type ExamplePlayerAlwaysJSON ExamplePlayerAlways + var parseObject ExamplePlayerAlwaysJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExamplePlayerAlways.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExamplePlayerAlways(parseObject) + + *p = v + + return nil +} + +// uuid.UUID +// +// OpenAPI Component Schema: PlayerId +type ExamplePlayerID uuid.UUID + +// ExamplePlayerMaybe +// +// OpenAPI Component Schema: PlayerMaybe +type ExamplePlayerMaybe struct { + ID uuid.UUID `json:"id"` +} + +func (p *ExamplePlayerMaybe) UnmarshalJSON(b []byte) error { + var err error + var requiredCheck map[string]any + + err = json.Unmarshal(b, &requiredCheck) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExamplePlayerMaybe.UnmarshalJSON Required: `%v`: %w", string(b), err)} + } + + var validationErrors validation.Errors + + if _, ok := requiredCheck["id"]; !ok { + validationErrors.Add("id", ErrExampleMissingRequiredField) + } + + if validationErrors != nil { + return validationErrors.GetErr() + } + + type ExamplePlayerMaybeJSON ExamplePlayerMaybe + var parseObject ExamplePlayerMaybeJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExamplePlayerMaybe.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExamplePlayerMaybe(parseObject) + + *p = v + + return nil +} + +// ExampleSeason +// +// OpenAPI Component Schema: Season + +// ExampleSeason +// Component Schema : Season +type ExampleSeason int8 + +const ( + UnknownExampleSeason ExampleSeason = iota + ExampleSeasonSpring + ExampleSeasonSummer + ExampleSeasonFall + ExampleSeasonWinter +) + +func NewExampleSeason(name string) ExampleSeason { + switch name { + case "spring": + return ExampleSeasonSpring + case "summer": + return ExampleSeasonSummer + case "fall": + return ExampleSeasonFall + case "winter": + return ExampleSeasonWinter + } + + return ExampleSeason(0) +} + +var ExampleSeasonString = map[ExampleSeason]string{ + ExampleSeasonSpring: "spring", + ExampleSeasonSummer: "summer", + ExampleSeasonFall: "fall", + ExampleSeasonWinter: "winter", +} + +func (e ExampleSeason) String() string { + return ExampleSeasonString[e] +} + +func (e *ExampleSeason) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleSeason(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleSeason(s) + + return nil +} + +func (e ExampleSeason) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleSeason) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleSeason) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleSeason.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleSeason(s) + + return nil +} + +// ExampleSeasonNullable +// +// OpenAPI Component Schema: SeasonNullable + +// ExampleSeasonNullable +// Component Schema : SeasonNullable +type ExampleSeasonNullable int8 + +const ( + UnknownExampleSeasonNullable ExampleSeasonNullable = iota + ExampleSeasonNullableSpring + ExampleSeasonNullableSummer + ExampleSeasonNullableFall + ExampleSeasonNullableWinter +) + +func NewExampleSeasonNullable(name string) ExampleSeasonNullable { + switch name { + case "spring": + return ExampleSeasonNullableSpring + case "summer": + return ExampleSeasonNullableSummer + case "fall": + return ExampleSeasonNullableFall + case "winter": + return ExampleSeasonNullableWinter + } + + return ExampleSeasonNullable(0) +} + +var ExampleSeasonNullableString = map[ExampleSeasonNullable]string{ + ExampleSeasonNullableSpring: "spring", + ExampleSeasonNullableSummer: "summer", + ExampleSeasonNullableFall: "fall", + ExampleSeasonNullableWinter: "winter", +} + +func (e ExampleSeasonNullable) String() string { + return ExampleSeasonNullableString[e] +} + +func (e *ExampleSeasonNullable) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleSeasonNullable(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleSeasonNullable(s) + + return nil +} + +func (e ExampleSeasonNullable) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleSeasonNullable) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleSeasonNullable) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleSeasonNullable.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleSeasonNullable(s) + + return nil +} + +// string +// +// OpenAPI Component Schema: State +type ExampleState string + +var exampleStatePattern = regexp.MustCompile(`(enabled|disabled)`) + +func (p *ExampleState) UnmarshalJSON(b []byte) error { + var err error + + type ExampleStateJSON ExampleState + var parseObject ExampleStateJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleState.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleState(parseObject) + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExampleState) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExampleState // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExampleState.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExampleState) Validate() error { + var err validation.Errors + + if len(p) < 2 { + _ = err.Add("", "length must be >= 2") + } + + if len(p) > 10 { + _ = err.Add("", "length must be <= 10") + } + + if p != "" && !exampleStatePattern.MatchString(string(p)) { + _ = err.Add("", `must match "(enabled|disabled)"`) + } + + return err.GetErr() +} + +// ExampleSubPattern +// +// OpenAPI Component Schema: SubPattern +type ExampleSubPattern struct { + Num int32 `json:"num,omitempty"` +} + +func (p *ExampleSubPattern) UnmarshalJSON(b []byte) error { + var err error + + type ExampleSubPatternJSON ExampleSubPattern + var parseObject ExampleSubPatternJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleSubPattern.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleSubPattern(parseObject) + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExampleSubPattern) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExampleSubPattern // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExampleSubPattern.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExampleSubPattern) Validate() error { + var err validation.Errors + + p.ValidateNum(&err) + + return err.GetErr() +} + +func (p ExampleSubPattern) ValidateNum(err *validation.Errors) { + if p.Num < 2 { + _ = err.Add("num", "must be >= 2") + } +} + +// []ExampleXarrayEnum +// +// OpenAPI Component Schema: XArrayEnum +type ExampleXarrayEnum []ExampleXarrayEnumItem + +// ExampleXarrayEnumItem +// +// OpenAPI []ExampleXarrayEnum inline item XArrayEnum: XarrayEnumItem + +// ExampleXarrayEnumItem +// []ExampleXarrayEnum inline item XArrayEnum : XarrayEnumItem +type ExampleXarrayEnumItem int8 + +const ( + UnknownExampleXarrayEnumItem ExampleXarrayEnumItem = iota + ExampleXarrayEnumItemOptionA + ExampleXarrayEnumItemOptionB + ExampleXarrayEnumItemOptionC +) + +func NewExampleXarrayEnumItem(name string) ExampleXarrayEnumItem { + switch name { + case "option_a": + return ExampleXarrayEnumItemOptionA + case "option_b": + return ExampleXarrayEnumItemOptionB + case "option_c": + return ExampleXarrayEnumItemOptionC + } + + return ExampleXarrayEnumItem(0) +} + +var ExampleXarrayEnumItemString = map[ExampleXarrayEnumItem]string{ + ExampleXarrayEnumItemOptionA: "option_a", + ExampleXarrayEnumItemOptionB: "option_b", + ExampleXarrayEnumItemOptionC: "option_c", +} + +func (e ExampleXarrayEnumItem) String() string { + return ExampleXarrayEnumItemString[e] +} + +func (e *ExampleXarrayEnumItem) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleXarrayEnumItem(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleXarrayEnumItem(s) + + return nil +} + +func (e ExampleXarrayEnumItem) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleXarrayEnumItem) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleXarrayEnumItem) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleXarrayEnumItem.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleXarrayEnumItem(s) + + return nil +} + +// []ExampleXarrayObjectArrayEnum +// +// OpenAPI Component Schema: XArrayObjectArrayEnum +type ExampleXarrayObjectArrayEnum []ExampleXarrayObjectArrayEnumItem + +// ExampleXarrayObjectArrayEnumItem +// +// OpenAPI []ExampleXarrayObjectArrayEnum inline item XArrayObjectArrayEnum: XarrayObjectArrayEnumItem +type ExampleXarrayObjectArrayEnumItem struct { + List []ExampleXarrayObjectArrayEnumItemList `json:"list,omitempty"` +} + +// ExampleXarrayObjectArrayEnumItemList +// +// OpenAPI ExampleXarrayObjectArrayEnumItem inline item list: XarrayObjectArrayEnumItemList + +// ExampleXarrayObjectArrayEnumItemList +// ExampleXarrayObjectArrayEnumItem inline item list : XarrayObjectArrayEnumItemList +type ExampleXarrayObjectArrayEnumItemList int8 + +const ( + UnknownExampleXarrayObjectArrayEnumItemList ExampleXarrayObjectArrayEnumItemList = iota + ExampleXarrayObjectArrayEnumItemListOptionA + ExampleXarrayObjectArrayEnumItemListOptionB + ExampleXarrayObjectArrayEnumItemListOptionC +) + +func NewExampleXarrayObjectArrayEnumItemList(name string) ExampleXarrayObjectArrayEnumItemList { + switch name { + case "option_a": + return ExampleXarrayObjectArrayEnumItemListOptionA + case "option_b": + return ExampleXarrayObjectArrayEnumItemListOptionB + case "option_c": + return ExampleXarrayObjectArrayEnumItemListOptionC + } + + return ExampleXarrayObjectArrayEnumItemList(0) +} + +var ExampleXarrayObjectArrayEnumItemListString = map[ExampleXarrayObjectArrayEnumItemList]string{ + ExampleXarrayObjectArrayEnumItemListOptionA: "option_a", + ExampleXarrayObjectArrayEnumItemListOptionB: "option_b", + ExampleXarrayObjectArrayEnumItemListOptionC: "option_c", +} + +func (e ExampleXarrayObjectArrayEnumItemList) String() string { + return ExampleXarrayObjectArrayEnumItemListString[e] +} + +func (e *ExampleXarrayObjectArrayEnumItemList) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleXarrayObjectArrayEnumItemList(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleXarrayObjectArrayEnumItemList(s) + + return nil +} + +func (e ExampleXarrayObjectArrayEnumItemList) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleXarrayObjectArrayEnumItemList) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleXarrayObjectArrayEnumItemList) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleXarrayObjectArrayEnumItemList.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleXarrayObjectArrayEnumItemList(s) + + return nil +} + +// []ExampleXarrayObjectEnum +// +// OpenAPI Component Schema: XArrayObjectEnum +type ExampleXarrayObjectEnum []ExampleXarrayObjectEnumItem + +// ExampleXarrayObjectEnumItem +// +// OpenAPI []ExampleXarrayObjectEnum inline item XArrayObjectEnum: XarrayObjectEnumItem +type ExampleXarrayObjectEnumItem struct { + Options ExampleXarrayObjectEnumItemOptions `json:"options,omitempty,omitzero"` +} + +// ExampleXarrayObjectEnumItemOptions +// []ExampleXarrayObjectEnum inline item XArrayObjectEnum : options +type ExampleXarrayObjectEnumItemOptions int8 + +const ( + UnknownExampleXarrayObjectEnumItemOptions ExampleXarrayObjectEnumItemOptions = iota + ExampleXarrayObjectEnumItemOptionsOptionA + ExampleXarrayObjectEnumItemOptionsOptionB + ExampleXarrayObjectEnumItemOptionsOptionC +) + +func NewExampleXarrayObjectEnumItemOptions(name string) ExampleXarrayObjectEnumItemOptions { + switch name { + case "option_a": + return ExampleXarrayObjectEnumItemOptionsOptionA + case "option_b": + return ExampleXarrayObjectEnumItemOptionsOptionB + case "option_c": + return ExampleXarrayObjectEnumItemOptionsOptionC + } + + return ExampleXarrayObjectEnumItemOptions(0) +} + +var ExampleXarrayObjectEnumItemOptionsString = map[ExampleXarrayObjectEnumItemOptions]string{ + ExampleXarrayObjectEnumItemOptionsOptionA: "option_a", + ExampleXarrayObjectEnumItemOptionsOptionB: "option_b", + ExampleXarrayObjectEnumItemOptionsOptionC: "option_c", +} + +func (e ExampleXarrayObjectEnumItemOptions) String() string { + return ExampleXarrayObjectEnumItemOptionsString[e] +} + +func (e *ExampleXarrayObjectEnumItemOptions) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleXarrayObjectEnumItemOptions(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleXarrayObjectEnumItemOptions(s) + + return nil +} + +func (e ExampleXarrayObjectEnumItemOptions) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleXarrayObjectEnumItemOptions) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleXarrayObjectEnumItemOptions) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleXarrayObjectEnumItemOptions.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleXarrayObjectEnumItemOptions(s) + + return nil +} + +// ExampleXobjectArrayEnum +// +// OpenAPI Component Schema: XObjectArrayEnum +type ExampleXobjectArrayEnum struct { + Items []ExampleXobjectArrayEnumItems `json:"items,omitempty"` +} + +// ExampleXobjectArrayEnumItems +// +// OpenAPI ExampleXobjectArrayEnum inline item items: XobjectArrayEnumItems + +// ExampleXobjectArrayEnumItems +// ExampleXobjectArrayEnum inline item items : XobjectArrayEnumItems +type ExampleXobjectArrayEnumItems int8 + +const ( + UnknownExampleXobjectArrayEnumItems ExampleXobjectArrayEnumItems = iota + ExampleXobjectArrayEnumItemsOptionA + ExampleXobjectArrayEnumItemsOptionB + ExampleXobjectArrayEnumItemsOptionC +) + +func NewExampleXobjectArrayEnumItems(name string) ExampleXobjectArrayEnumItems { + switch name { + case "option_a": + return ExampleXobjectArrayEnumItemsOptionA + case "option_b": + return ExampleXobjectArrayEnumItemsOptionB + case "option_c": + return ExampleXobjectArrayEnumItemsOptionC + } + + return ExampleXobjectArrayEnumItems(0) +} + +var ExampleXobjectArrayEnumItemsString = map[ExampleXobjectArrayEnumItems]string{ + ExampleXobjectArrayEnumItemsOptionA: "option_a", + ExampleXobjectArrayEnumItemsOptionB: "option_b", + ExampleXobjectArrayEnumItemsOptionC: "option_c", +} + +func (e ExampleXobjectArrayEnumItems) String() string { + return ExampleXobjectArrayEnumItemsString[e] +} + +func (e *ExampleXobjectArrayEnumItems) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleXobjectArrayEnumItems(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleXobjectArrayEnumItems(s) + + return nil +} + +func (e ExampleXobjectArrayEnumItems) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleXobjectArrayEnumItems) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleXobjectArrayEnumItems) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleXobjectArrayEnumItems.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleXobjectArrayEnumItems(s) + + return nil +} + +// ExampleXobjectArrayObjectEnum +// +// OpenAPI Component Schema: XObjectArrayObjectEnum +type ExampleXobjectArrayObjectEnum struct { + List []ExampleXobjectArrayObjectEnumList `json:"list,omitempty"` +} + +// ExampleXobjectArrayObjectEnumList +// +// OpenAPI ExampleXobjectArrayObjectEnum inline item list: XobjectArrayObjectEnumList +type ExampleXobjectArrayObjectEnumList struct { + Options ExampleXobjectArrayObjectEnumListOptions `json:"options,omitempty,omitzero"` +} + +// ExampleXobjectArrayObjectEnumListOptions +// ExampleXobjectArrayObjectEnum inline item list : options +type ExampleXobjectArrayObjectEnumListOptions int8 + +const ( + UnknownExampleXobjectArrayObjectEnumListOptions ExampleXobjectArrayObjectEnumListOptions = iota + ExampleXobjectArrayObjectEnumListOptionsOptionA + ExampleXobjectArrayObjectEnumListOptionsOptionB + ExampleXobjectArrayObjectEnumListOptionsOptionC +) + +func NewExampleXobjectArrayObjectEnumListOptions(name string) ExampleXobjectArrayObjectEnumListOptions { + switch name { + case "option_a": + return ExampleXobjectArrayObjectEnumListOptionsOptionA + case "option_b": + return ExampleXobjectArrayObjectEnumListOptionsOptionB + case "option_c": + return ExampleXobjectArrayObjectEnumListOptionsOptionC + } + + return ExampleXobjectArrayObjectEnumListOptions(0) +} + +var ExampleXobjectArrayObjectEnumListOptionsString = map[ExampleXobjectArrayObjectEnumListOptions]string{ + ExampleXobjectArrayObjectEnumListOptionsOptionA: "option_a", + ExampleXobjectArrayObjectEnumListOptionsOptionB: "option_b", + ExampleXobjectArrayObjectEnumListOptionsOptionC: "option_c", +} + +func (e ExampleXobjectArrayObjectEnumListOptions) String() string { + return ExampleXobjectArrayObjectEnumListOptionsString[e] +} + +func (e *ExampleXobjectArrayObjectEnumListOptions) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleXobjectArrayObjectEnumListOptions(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleXobjectArrayObjectEnumListOptions(s) + + return nil +} + +func (e ExampleXobjectArrayObjectEnumListOptions) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleXobjectArrayObjectEnumListOptions) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleXobjectArrayObjectEnumListOptions) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleXobjectArrayObjectEnumListOptions.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleXobjectArrayObjectEnumListOptions(s) + + return nil +} + +// ExampleXobjectEnum +// +// OpenAPI Component Schema: XObjectEnum +type ExampleXobjectEnum struct { + Options ExampleXobjectEnumOptions `json:"options,omitempty,omitzero"` +} + +// ExampleXobjectEnumOptions +// Component Schema : options +type ExampleXobjectEnumOptions int8 + +const ( + UnknownExampleXobjectEnumOptions ExampleXobjectEnumOptions = iota + ExampleXobjectEnumOptionsOptionA + ExampleXobjectEnumOptionsOptionB + ExampleXobjectEnumOptionsOptionC +) + +func NewExampleXobjectEnumOptions(name string) ExampleXobjectEnumOptions { + switch name { + case "option_a": + return ExampleXobjectEnumOptionsOptionA + case "option_b": + return ExampleXobjectEnumOptionsOptionB + case "option_c": + return ExampleXobjectEnumOptionsOptionC + } + + return ExampleXobjectEnumOptions(0) +} + +var ExampleXobjectEnumOptionsString = map[ExampleXobjectEnumOptions]string{ + ExampleXobjectEnumOptionsOptionA: "option_a", + ExampleXobjectEnumOptionsOptionB: "option_b", + ExampleXobjectEnumOptionsOptionC: "option_c", +} + +func (e ExampleXobjectEnumOptions) String() string { + return ExampleXobjectEnumOptionsString[e] +} + +func (e *ExampleXobjectEnumOptions) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleXobjectEnumOptions(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleXobjectEnumOptions(s) + + return nil +} + +func (e ExampleXobjectEnumOptions) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleXobjectEnumOptions) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleXobjectEnumOptions) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleXobjectEnumOptions.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleXobjectEnumOptions(s) + + return nil +} + +// ExampleErrorV1 +// Standard error format +// +// OpenAPI Component Schema: errorV1 +type ExampleErrorV1 struct { + Code string `json:"code,omitempty,omitzero"` + Message string `json:"message,omitempty,omitzero"` +} + +// ExampleExampleKebabCaseField +// +// OpenAPI Component Schema: example-kebab-case-field +type ExampleExampleKebabCaseField struct { + Seniority string `json:"seniority,omitempty,omitzero"` + Tier string `json:"tier,omitempty,omitzero"` +} + +var ( + exampleExampleKebabCaseFieldSeniorityPattern = regexp.MustCompile(`(^[1-9]\d*$)`) + exampleExampleKebabCaseFieldTierPattern = regexp.MustCompile(`(alpha|beta|gama)`) +) + +func (p *ExampleExampleKebabCaseField) UnmarshalJSON(b []byte) error { + var err error + + type ExampleExampleKebabCaseFieldJSON ExampleExampleKebabCaseField + var parseObject ExampleExampleKebabCaseFieldJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleExampleKebabCaseField.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleExampleKebabCaseField(parseObject) + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExampleExampleKebabCaseField) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExampleExampleKebabCaseField // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExampleExampleKebabCaseField.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExampleExampleKebabCaseField) Validate() error { + var err validation.Errors + + p.ValidateSeniority(&err) + p.ValidateTier(&err) + + return err.GetErr() +} + +func (p ExampleExampleKebabCaseField) ValidateSeniority(err *validation.Errors) { + if p.Seniority != "" && !exampleExampleKebabCaseFieldSeniorityPattern.MatchString(string(p.Seniority)) { + _ = err.Add("seniority", `must match "(^[1-9]\d*$)"`) + } +} + +func (p ExampleExampleKebabCaseField) ValidateTier(err *validation.Errors) { + if p.Tier != "" && !exampleExampleKebabCaseFieldTierPattern.MatchString(string(p.Tier)) { + _ = err.Add("tier", `must match "(alpha|beta|gama)"`) + } +} + +// Component Parameters + +// ExampleColorQuery +// Component Parameter: color +type ExampleColorQuery int8 + +const ( + UnknownExampleColorQuery ExampleColorQuery = iota + ExampleColorQueryRed + ExampleColorQueryGreen + ExampleColorQueryBlue +) + +func NewExampleColorQuery(name string) ExampleColorQuery { + switch name { + case "red": + return ExampleColorQueryRed + case "green": + return ExampleColorQueryGreen + case "blue": + return ExampleColorQueryBlue + } + + return ExampleColorQuery(0) +} + +var ExampleColorQueryString = map[ExampleColorQuery]string{ + ExampleColorQueryRed: "red", + ExampleColorQueryGreen: "green", + ExampleColorQueryBlue: "blue", +} + +func (e ExampleColorQuery) String() string { + return ExampleColorQueryString[e] +} + +func (e *ExampleColorQuery) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleColorQuery(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleColorQuery(s) + + return nil +} + +func (e ExampleColorQuery) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleColorQuery) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleColorQuery) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleColorQuery.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleColorQuery(s) + + return nil +} + +// ExampleColorQueryDefault +// Component Parameter: colorDefault +type ExampleColorQueryDefault int8 + +const ( + UnknownExampleColorQueryDefault ExampleColorQueryDefault = iota + ExampleColorQueryDefaultRed + ExampleColorQueryDefaultGreen + ExampleColorQueryDefaultBlue +) + +func NewExampleColorQueryDefault(name string) ExampleColorQueryDefault { + switch name { + case "red": + return ExampleColorQueryDefaultRed + case "green": + return ExampleColorQueryDefaultGreen + case "blue": + return ExampleColorQueryDefaultBlue + } + + return ExampleColorQueryDefault(0) +} + +var ExampleColorQueryDefaultString = map[ExampleColorQueryDefault]string{ + ExampleColorQueryDefaultRed: "red", + ExampleColorQueryDefaultGreen: "green", + ExampleColorQueryDefaultBlue: "blue", +} + +func (e ExampleColorQueryDefault) String() string { + return ExampleColorQueryDefaultString[e] +} + +func (e *ExampleColorQueryDefault) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleColorQueryDefault(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleColorQueryDefault(s) + + return nil +} + +func (e ExampleColorQueryDefault) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleColorQueryDefault) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleColorQueryDefault) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleColorQueryDefault.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleColorQueryDefault(s) + + return nil +} + +// Path Operations + +// ExampleAddFormRequest +// +// OpenAPI AddForm Body: AddForm Request +type ExampleAddFormRequest struct { + F01 bool `json:"f01,omitempty"` + F01Null *bool `json:"f01Null,omitempty"` + F01B bool `json:"f01b,omitempty"` + F01BNull *bool `json:"f01bNull,omitempty"` + F02 int32 `json:"f02,omitempty"` + F02Null *int32 `json:"f02Null,omitempty"` + F03 int32 `json:"f03,omitempty"` + F03Null *int32 `json:"f03Null,omitempty"` + F04 int64 `json:"f04,omitempty"` + F04Null *int64 `json:"f04Null,omitempty"` + F05 time.Time `json:"f05,omitempty,omitzero"` + F05Null *time.Time `json:"f05Null,omitempty,omitzero"` + F06 uuid.UUID `json:"f06,omitempty,omitzero"` + F06Null *uuid.UUID `json:"f06Null,omitempty,omitzero"` + F07 string `json:"f07"` + F07Null *string `json:"f07Null,omitempty,omitzero"` + F08 ExampleAddFormRequestF08 `json:"f08,omitempty,omitzero"` + F08Null *ExampleAddFormRequestF08Null `json:"f08Null,omitempty,omitzero"` + F09 ExampleSeason `json:"f09,omitempty,omitzero"` + F09Null *ExampleSeasonNullable `json:"f09Null,omitempty,omitzero"` + F10 []string `json:"f10,omitempty"` + F11 []int32 `json:"f11,omitempty"` + F12 []ExampleSeason `json:"f12,omitempty"` + F13 string `json:"f13,omitempty,omitzero"` + F13Null *string `json:"f13Null,omitempty,omitzero"` +} + +// ExampleAddFormRequestF08 +// AddForm Body : f08 +type ExampleAddFormRequestF08 int8 + +const ( + UnknownExampleAddFormRequestF08 ExampleAddFormRequestF08 = iota + ExampleAddFormRequestF08ValueA + ExampleAddFormRequestF08ValueB + ExampleAddFormRequestF08ValueC +) + +func NewExampleAddFormRequestF08(name string) ExampleAddFormRequestF08 { + switch name { + case "valueA": + return ExampleAddFormRequestF08ValueA + case "valueB": + return ExampleAddFormRequestF08ValueB + case "valueC": + return ExampleAddFormRequestF08ValueC + } + + return ExampleAddFormRequestF08(0) +} + +var ExampleAddFormRequestF08String = map[ExampleAddFormRequestF08]string{ + ExampleAddFormRequestF08ValueA: "valueA", + ExampleAddFormRequestF08ValueB: "valueB", + ExampleAddFormRequestF08ValueC: "valueC", +} + +func (e ExampleAddFormRequestF08) String() string { + return ExampleAddFormRequestF08String[e] +} + +func (e *ExampleAddFormRequestF08) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleAddFormRequestF08(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleAddFormRequestF08(s) + + return nil +} + +func (e ExampleAddFormRequestF08) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleAddFormRequestF08) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleAddFormRequestF08) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleAddFormRequestF08.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleAddFormRequestF08(s) + + return nil +} + +// ExampleAddFormRequestF08Null +// AddForm Body : f08Null +type ExampleAddFormRequestF08Null int8 + +const ( + UnknownExampleAddFormRequestF08Null ExampleAddFormRequestF08Null = iota + ExampleAddFormRequestF08NullValueA + ExampleAddFormRequestF08NullValueB + ExampleAddFormRequestF08NullValueC +) + +func NewExampleAddFormRequestF08Null(name string) ExampleAddFormRequestF08Null { + switch name { + case "valueA": + return ExampleAddFormRequestF08NullValueA + case "valueB": + return ExampleAddFormRequestF08NullValueB + case "valueC": + return ExampleAddFormRequestF08NullValueC + } + + return ExampleAddFormRequestF08Null(0) +} + +var ExampleAddFormRequestF08NullString = map[ExampleAddFormRequestF08Null]string{ + ExampleAddFormRequestF08NullValueA: "valueA", + ExampleAddFormRequestF08NullValueB: "valueB", + ExampleAddFormRequestF08NullValueC: "valueC", +} + +func (e ExampleAddFormRequestF08Null) String() string { + return ExampleAddFormRequestF08NullString[e] +} + +func (e *ExampleAddFormRequestF08Null) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleAddFormRequestF08Null(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleAddFormRequestF08Null(s) + + return nil +} + +func (e ExampleAddFormRequestF08Null) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleAddFormRequestF08Null) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleAddFormRequestF08Null) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleAddFormRequestF08Null.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleAddFormRequestF08Null(s) + + return nil +} + +func ParseFormExampleAddFormRequest(r *http.Request) (ExampleAddFormRequest, error) { + var ( + parseErrors validation.Errors + err error + v ExampleAddFormRequest + ) + + paramF01, ok, err := forms.GetBool(r.FormValue, "f01", false) + if err != nil { + parseErrors.Add("f01", err) + } else if ok { + v.F01 = paramF01 + } + + paramF01Null, ok, err := forms.GetBool(r.FormValue, "f01Null", false) + if err != nil { + parseErrors.Add("f01Null", err) + } else if ok { + v.F01Null = ¶mF01Null + } + + paramF01B, ok, err := forms.GetBool(r.FormValue, "f01b", false) + if err != nil { + parseErrors.Add("f01b", err) + } else if !ok { + paramF01B = true + } + + v.F01B = paramF01B + + paramF01BNull, ok, err := forms.GetBool(r.FormValue, "f01bNull", false) + if err != nil { + parseErrors.Add("f01bNull", err) + } else if !ok { + paramF01BNull = true + } + + v.F01BNull = ¶mF01BNull + + paramF02, ok, err := forms.GetInt32(r.FormValue, "f02", false) + if err != nil { + parseErrors.Add("f02", err) + } else if ok { + v.F02 = paramF02 + } + + paramF02Null, ok, err := forms.GetInt32(r.FormValue, "f02Null", false) + if err != nil { + parseErrors.Add("f02Null", err) + } else if ok { + v.F02Null = ¶mF02Null + } + + paramF03, ok, err := forms.GetInt32(r.FormValue, "f03", false) + if err != nil { + parseErrors.Add("f03", err) + } else if ok { + v.F03 = paramF03 + } + + paramF03Null, ok, err := forms.GetInt32(r.FormValue, "f03Null", false) + if err != nil { + parseErrors.Add("f03Null", err) + } else if ok { + v.F03Null = ¶mF03Null + } + + paramF04, ok, err := forms.GetInt64(r.FormValue, "f04", false) + if err != nil { + parseErrors.Add("f04", err) + } else if !ok { + paramF04 = 1 + } + + v.F04 = paramF04 + + paramF04Null, ok, err := forms.GetInt64(r.FormValue, "f04Null", false) + if err != nil { + parseErrors.Add("f04Null", err) + } else if !ok { + paramF04Null = 2 + } + + v.F04Null = ¶mF04Null + + paramF05, ok, err := forms.GetTime(r.FormValue, "f05", false) + if err != nil { + parseErrors.Add("f05", err) + } else if ok { + v.F05 = paramF05 + } + + paramF05Null, ok, err := forms.GetTime(r.FormValue, "f05Null", false) + if err != nil { + parseErrors.Add("f05Null", err) + } else if ok { + v.F05Null = ¶mF05Null + } + + paramF06, ok, err := forms.GetUUID(r.FormValue, "f06", false) + if err != nil { + parseErrors.Add("f06", err) + } else if ok { + v.F06 = paramF06 + } + + paramF06Null, ok, err := forms.GetUUID(r.FormValue, "f06Null", false) + if err != nil { + parseErrors.Add("f06Null", err) + } else if ok { + v.F06Null = ¶mF06Null + } + + paramF07, ok, err := forms.GetString(r.FormValue, "f07", true) + if err != nil { + parseErrors.Add("f07", err) + } else if ok { + v.F07 = paramF07 + } + + paramF07Null, ok, err := forms.GetString(r.FormValue, "f07Null", false) + if err != nil { + parseErrors.Add("f07Null", err) + } else if ok { + v.F07Null = ¶mF07Null + } + + paramF08, ok, err := forms.GetEnum(r.FormValue, "f08", false, NewExampleAddFormRequestF08) + if err != nil { + parseErrors.Add("f08", err) + } else if !ok { + paramF08 = ExampleAddFormRequestF08ValueA + } + + v.F08 = paramF08 + + paramF08Null, ok, err := forms.GetEnum(r.FormValue, "f08Null", false, NewExampleAddFormRequestF08Null) + if err != nil { + parseErrors.Add("f08Null", err) + } else if !ok { + paramF08Null = ExampleAddFormRequestF08NullValueB + } + + v.F08Null = ¶mF08Null + + paramF09, ok, err := forms.GetEnum(r.FormValue, "f09", false, NewExampleSeason) + if err != nil { + parseErrors.Add("f09", err) + } else if ok { + v.F09 = paramF09 + } + + paramF09Null, ok, err := forms.GetEnum(r.FormValue, "f09Null", false, NewExampleSeasonNullable) + if err != nil { + parseErrors.Add("f09Null", err) + } else if ok { + v.F09Null = ¶mF09Null + } + + v.F10, _, err = forms.GetStringArray(r.FormValue, "f10", false) + if err != nil { + parseErrors.Add("f10", err) + } + + v.F11, _, err = forms.GetInt32Array(r.FormValue, "f11", false) + if err != nil { + parseErrors.Add("f11", err) + } + + v.F12, _, err = forms.GetEnumArray(r.FormValue, "f12", false, NewExampleSeason) + if err != nil { + parseErrors.Add("f12", err) + } + + paramF13, ok, err := forms.GetString(r.FormValue, "f13", false) + if err != nil { + parseErrors.Add("f13", err) + } else if !ok { + paramF13 = "someValue" + } + + v.F13 = paramF13 + + paramF13Null, ok, err := forms.GetString(r.FormValue, "f13Null", false) + if err != nil { + parseErrors.Add("f13Null", err) + } else if !ok { + paramF13Null = "someValue2" + } + + v.F13Null = ¶mF13Null + + if parseErrors != nil { + return ExampleAddFormRequest{}, parseErrors.GetErr() + } + + err = v.Validate() + if err != nil { + return ExampleAddFormRequest{}, err + } + + return v, nil +} + +func (p ExampleAddFormRequest) Validate() error { + var err validation.Errors + + p.ValidateF10(&err) + + return err.GetErr() +} + +func (p ExampleAddFormRequest) ValidateF10(err *validation.Errors) { + if len(p.F10) < 1 { + _ = err.Add("f10", "length must be >= 1") + } +} + +// ExampleAddMultipartFormRequest +// +// OpenAPI AddMultipartForm Body: AddMultipartForm Request +type ExampleAddMultipartFormRequest struct { + F1 bool `json:"f1,omitempty"` + F2 int32 `json:"f2,omitempty"` + F3 int32 `json:"f3,omitempty"` + F4 int64 `json:"f4,omitempty"` + F5 time.Time `json:"f5,omitempty,omitzero"` + F6 uuid.UUID `json:"f6,omitempty,omitzero"` + F7 string `json:"f7,omitempty,omitzero"` + File1 forms.File `json:"file1"` + File2 forms.File `json:"file2,omitempty,omitzero"` +} + +func ParseFormExampleAddMultipartFormRequest(r *http.Request) (ExampleAddMultipartFormRequest, error) { + var ( + parseErrors validation.Errors + err error + v ExampleAddMultipartFormRequest + ) + + paramF1, ok, err := forms.GetBool(r.FormValue, "f1", false) + if err != nil { + parseErrors.Add("f1", err) + } else if ok { + v.F1 = paramF1 + } + + paramF2, ok, err := forms.GetInt32(r.FormValue, "f2", false) + if err != nil { + parseErrors.Add("f2", err) + } else if ok { + v.F2 = paramF2 + } + + paramF3, ok, err := forms.GetInt32(r.FormValue, "f3", false) + if err != nil { + parseErrors.Add("f3", err) + } else if ok { + v.F3 = paramF3 + } + + paramF4, ok, err := forms.GetInt64(r.FormValue, "f4", false) + if err != nil { + parseErrors.Add("f4", err) + } else if ok { + v.F4 = paramF4 + } + + paramF5, ok, err := forms.GetTime(r.FormValue, "f5", false) + if err != nil { + parseErrors.Add("f5", err) + } else if ok { + v.F5 = paramF5 + } + + paramF6, ok, err := forms.GetUUID(r.FormValue, "f6", false) + if err != nil { + parseErrors.Add("f6", err) + } else if ok { + v.F6 = paramF6 + } + + paramF7, ok, err := forms.GetString(r.FormValue, "f7", false) + if err != nil { + parseErrors.Add("f7", err) + } else if ok { + v.F7 = paramF7 + } + + paramFile1, ok, err := forms.GetFile(r, "file1", true) + if err != nil { + parseErrors.Add("file1", err) + } else if ok { + v.File1 = paramFile1 + } + + paramFile2, ok, err := forms.GetFile(r, "file2", false) + if err != nil { + parseErrors.Add("file2", err) + } else if ok { + v.File2 = paramFile2 + } + + if parseErrors != nil { + return ExampleAddMultipartFormRequest{}, parseErrors.GetErr() + } + + return v, nil +} + +// ExampleAddInlinedAllOfRequest +// +// OpenAPI AddInlinedAllOf Body: AddInlinedAllOf Request +type ExampleAddInlinedAllOfRequest struct { + Foos ExampleFooString `json:"foos,omitempty,omitzero"` + Special bool `json:"special,omitempty"` +} + +func (p *ExampleAddInlinedAllOfRequest) UnmarshalJSON(b []byte) error { + var err error + + type ExampleAddInlinedAllOfRequestJSON ExampleAddInlinedAllOfRequest + var parseObject ExampleAddInlinedAllOfRequestJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleAddInlinedAllOfRequest.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleAddInlinedAllOfRequest(parseObject) + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExampleAddInlinedAllOfRequest) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExampleAddInlinedAllOfRequest // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExampleAddInlinedAllOfRequest.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExampleAddInlinedAllOfRequest) Validate() error { + var err validation.Errors + + p.ValidateFoos(&err) + + return err.GetErr() +} + +func (p ExampleAddInlinedAllOfRequest) ValidateFoos(err *validation.Errors) { + if subErr := p.Foos.Validate(); subErr != nil { + _ = err.Add("foos", subErr) + } +} + +// ExampleAddInlinedBodyRequest +// +// OpenAPI AddInlinedBody Body: AddInlinedBody Request +type ExampleAddInlinedBodyRequest struct { + F01 bool `json:"f01,omitempty"` + F01Null *bool `json:"f01Null,omitempty"` + F01B bool `json:"f01b,omitempty"` + F01BNull *bool `json:"f01bNull,omitempty"` + F02 int32 `json:"f02,omitempty"` + F02Null *int32 `json:"f02Null,omitempty"` + F03 int32 `json:"f03,omitempty"` + F03Null *int32 `json:"f03Null,omitempty"` + F04 int64 `json:"f04,omitempty"` + F04Null *int64 `json:"f04Null,omitempty"` + F05 time.Time `json:"f05,omitempty,omitzero"` + F05Null *time.Time `json:"f05Null,omitempty,omitzero"` + F06 uuid.UUID `json:"f06,omitempty,omitzero"` + F06Null *uuid.UUID `json:"f06Null,omitempty,omitzero"` + F07 string `json:"f07"` + F07Null *string `json:"f07Null,omitempty,omitzero"` + F08 ExampleAddInlinedBodyRequestF08 `json:"f08,omitempty,omitzero"` + F08Null *ExampleAddInlinedBodyRequestF08Null `json:"f08Null,omitempty,omitzero"` + F09 ExampleSeason `json:"f09,omitempty,omitzero"` + F09Null *ExampleSeasonNullable `json:"f09Null,omitempty,omitzero"` + F10 []string `json:"f10"` + F11 []int32 `json:"f11,omitempty"` + F12 []ExampleSeason `json:"f12,omitempty"` + F13 string `json:"f13,omitempty,omitzero"` + F13Null *string `json:"f13Null,omitempty,omitzero"` +} + +// ExampleAddInlinedBodyRequestF08 +// AddInlinedBody Body : f08 +type ExampleAddInlinedBodyRequestF08 int8 + +const ( + UnknownExampleAddInlinedBodyRequestF08 ExampleAddInlinedBodyRequestF08 = iota + ExampleAddInlinedBodyRequestF08ValueA + ExampleAddInlinedBodyRequestF08ValueB + ExampleAddInlinedBodyRequestF08ValueC +) + +func NewExampleAddInlinedBodyRequestF08(name string) ExampleAddInlinedBodyRequestF08 { + switch name { + case "valueA": + return ExampleAddInlinedBodyRequestF08ValueA + case "valueB": + return ExampleAddInlinedBodyRequestF08ValueB + case "valueC": + return ExampleAddInlinedBodyRequestF08ValueC + } + + return ExampleAddInlinedBodyRequestF08(0) +} + +var ExampleAddInlinedBodyRequestF08String = map[ExampleAddInlinedBodyRequestF08]string{ + ExampleAddInlinedBodyRequestF08ValueA: "valueA", + ExampleAddInlinedBodyRequestF08ValueB: "valueB", + ExampleAddInlinedBodyRequestF08ValueC: "valueC", +} + +func (e ExampleAddInlinedBodyRequestF08) String() string { + return ExampleAddInlinedBodyRequestF08String[e] +} + +func (e *ExampleAddInlinedBodyRequestF08) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleAddInlinedBodyRequestF08(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleAddInlinedBodyRequestF08(s) + + return nil +} + +func (e ExampleAddInlinedBodyRequestF08) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleAddInlinedBodyRequestF08) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleAddInlinedBodyRequestF08) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleAddInlinedBodyRequestF08.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleAddInlinedBodyRequestF08(s) + + return nil +} + +// ExampleAddInlinedBodyRequestF08Null +// AddInlinedBody Body : f08Null +type ExampleAddInlinedBodyRequestF08Null int8 + +const ( + UnknownExampleAddInlinedBodyRequestF08Null ExampleAddInlinedBodyRequestF08Null = iota + ExampleAddInlinedBodyRequestF08NullValueA + ExampleAddInlinedBodyRequestF08NullValueB + ExampleAddInlinedBodyRequestF08NullValueC +) + +func NewExampleAddInlinedBodyRequestF08Null(name string) ExampleAddInlinedBodyRequestF08Null { + switch name { + case "valueA": + return ExampleAddInlinedBodyRequestF08NullValueA + case "valueB": + return ExampleAddInlinedBodyRequestF08NullValueB + case "valueC": + return ExampleAddInlinedBodyRequestF08NullValueC + } + + return ExampleAddInlinedBodyRequestF08Null(0) +} + +var ExampleAddInlinedBodyRequestF08NullString = map[ExampleAddInlinedBodyRequestF08Null]string{ + ExampleAddInlinedBodyRequestF08NullValueA: "valueA", + ExampleAddInlinedBodyRequestF08NullValueB: "valueB", + ExampleAddInlinedBodyRequestF08NullValueC: "valueC", +} + +func (e ExampleAddInlinedBodyRequestF08Null) String() string { + return ExampleAddInlinedBodyRequestF08NullString[e] +} + +func (e *ExampleAddInlinedBodyRequestF08Null) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleAddInlinedBodyRequestF08Null(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleAddInlinedBodyRequestF08Null(s) + + return nil +} + +func (e ExampleAddInlinedBodyRequestF08Null) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleAddInlinedBodyRequestF08Null) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleAddInlinedBodyRequestF08Null) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleAddInlinedBodyRequestF08Null.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleAddInlinedBodyRequestF08Null(s) + + return nil +} + +func (p *ExampleAddInlinedBodyRequest) UnmarshalJSON(b []byte) error { + var err error + var requiredCheck map[string]any + + err = json.Unmarshal(b, &requiredCheck) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleAddInlinedBodyRequest.UnmarshalJSON Required: `%v`: %w", string(b), err)} + } + + var validationErrors validation.Errors + + if _, ok := requiredCheck["f07"]; !ok { + validationErrors.Add("f07", ErrExampleMissingRequiredField) + } + + if _, ok := requiredCheck["f10"]; !ok { + validationErrors.Add("f10", ErrExampleMissingRequiredField) + } + + if validationErrors != nil { + return validationErrors.GetErr() + } + + type ExampleAddInlinedBodyRequestJSON ExampleAddInlinedBodyRequest + var parseObject ExampleAddInlinedBodyRequestJSON + + err = json.Unmarshal(b, &parseObject) + if err != nil { + return validation.Error{err.Error(), fmt.Errorf("ExampleAddInlinedBodyRequest.UnmarshalJSON: `%v`: %w", string(b), err)} + } + + v := ExampleAddInlinedBodyRequest(parseObject) + + if _, ok := requiredCheck["f01b"]; !ok { + v.F01B = true + } + + if _, ok := requiredCheck["f01bNull"]; !ok { + var defaultVal bool = true + v.F01BNull = &defaultVal + } + + if _, ok := requiredCheck["f04"]; !ok { + v.F04 = 1 + } + + if _, ok := requiredCheck["f04Null"]; !ok { + var defaultVal int64 = 2 + v.F04Null = &defaultVal + } + + if _, ok := requiredCheck["f08"]; !ok { + v.F08 = ExampleAddInlinedBodyRequestF08ValueA + } + + if _, ok := requiredCheck["f08Null"]; !ok { + defaultVal := ExampleAddInlinedBodyRequestF08NullValueB + v.F08Null = &defaultVal + } + + if _, ok := requiredCheck["f13"]; !ok { + v.F13 = "someValue" + } + + if _, ok := requiredCheck["f13Null"]; !ok { + var defaultVal string = "someValue2" + v.F13Null = &defaultVal + } + + err = v.Validate() + if err != nil { + return err + } + + *p = v + + return nil +} + +func (p ExampleAddInlinedBodyRequest) MarshalJSON() ([]byte, error) { + err := p.Validate() + if err != nil { + return nil, err + } + + type unvalidated ExampleAddInlinedBodyRequest // Skips the validation check + b, err := json.Marshal(unvalidated(p)) + if err != nil { + return nil, fmt.Errorf("ExampleAddInlinedBodyRequest.Marshal: `%+v`: %w", p, err) + } + + return b, nil +} + +func (p ExampleAddInlinedBodyRequest) Validate() error { + var err validation.Errors + + p.ValidateF10(&err) + + return err.GetErr() +} + +func (p ExampleAddInlinedBodyRequest) ValidateF10(err *validation.Errors) { + if len(p.F10) < 1 { + _ = err.Add("f10", "length must be >= 1") + } +} + +// ExampleGetExampleParamsEnumTest +// Enum Description +// Op: getExampleParams Param: enumTest +type ExampleGetExampleParamsEnumTest int8 + +const ( + UnknownExampleGetExampleParamsEnumTest ExampleGetExampleParamsEnumTest = iota + ExampleGetExampleParamsEnumTestValueA + ExampleGetExampleParamsEnumTestValueB +) + +func NewExampleGetExampleParamsEnumTest(name string) ExampleGetExampleParamsEnumTest { + switch name { + case "valueA": + return ExampleGetExampleParamsEnumTestValueA + case "valueB": + return ExampleGetExampleParamsEnumTestValueB + } + + return ExampleGetExampleParamsEnumTest(0) +} + +var ExampleGetExampleParamsEnumTestString = map[ExampleGetExampleParamsEnumTest]string{ + ExampleGetExampleParamsEnumTestValueA: "valueA", + ExampleGetExampleParamsEnumTestValueB: "valueB", +} + +func (e ExampleGetExampleParamsEnumTest) String() string { + return ExampleGetExampleParamsEnumTestString[e] +} + +func (e *ExampleGetExampleParamsEnumTest) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleGetExampleParamsEnumTest(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleGetExampleParamsEnumTest(s) + + return nil +} + +func (e ExampleGetExampleParamsEnumTest) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleGetExampleParamsEnumTest) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleGetExampleParamsEnumTest) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleGetExampleParamsEnumTest.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleGetExampleParamsEnumTest(s) + + return nil +} + +// ExampleGetRawRequestVehicle +// Op: getRawRequest Param: vehicle +type ExampleGetRawRequestVehicle int8 + +const ( + UnknownExampleGetRawRequestVehicle ExampleGetRawRequestVehicle = iota + ExampleGetRawRequestVehicleCar + ExampleGetRawRequestVehicleTruck + ExampleGetRawRequestVehicleBike +) + +func NewExampleGetRawRequestVehicle(name string) ExampleGetRawRequestVehicle { + switch name { + case "car": + return ExampleGetRawRequestVehicleCar + case "truck": + return ExampleGetRawRequestVehicleTruck + case "bike": + return ExampleGetRawRequestVehicleBike + } + + return ExampleGetRawRequestVehicle(0) +} + +var ExampleGetRawRequestVehicleString = map[ExampleGetRawRequestVehicle]string{ + ExampleGetRawRequestVehicleCar: "car", + ExampleGetRawRequestVehicleTruck: "truck", + ExampleGetRawRequestVehicleBike: "bike", +} + +func (e ExampleGetRawRequestVehicle) String() string { + return ExampleGetRawRequestVehicleString[e] +} + +func (e *ExampleGetRawRequestVehicle) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleGetRawRequestVehicle(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleGetRawRequestVehicle(s) + + return nil +} + +func (e ExampleGetRawRequestVehicle) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleGetRawRequestVehicle) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleGetRawRequestVehicle) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleGetRawRequestVehicle.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleGetRawRequestVehicle(s) + + return nil +} + +// ExampleGetRawRequestResponseVehicle +// Op: getRawRequestResponse Param: vehicle +type ExampleGetRawRequestResponseVehicle int8 + +const ( + UnknownExampleGetRawRequestResponseVehicle ExampleGetRawRequestResponseVehicle = iota + ExampleGetRawRequestResponseVehicleCar + ExampleGetRawRequestResponseVehicleTruck + ExampleGetRawRequestResponseVehicleBike +) + +func NewExampleGetRawRequestResponseVehicle(name string) ExampleGetRawRequestResponseVehicle { + switch name { + case "car": + return ExampleGetRawRequestResponseVehicleCar + case "truck": + return ExampleGetRawRequestResponseVehicleTruck + case "bike": + return ExampleGetRawRequestResponseVehicleBike + } + + return ExampleGetRawRequestResponseVehicle(0) +} + +var ExampleGetRawRequestResponseVehicleString = map[ExampleGetRawRequestResponseVehicle]string{ + ExampleGetRawRequestResponseVehicleCar: "car", + ExampleGetRawRequestResponseVehicleTruck: "truck", + ExampleGetRawRequestResponseVehicleBike: "bike", +} + +func (e ExampleGetRawRequestResponseVehicle) String() string { + return ExampleGetRawRequestResponseVehicleString[e] +} + +func (e *ExampleGetRawRequestResponseVehicle) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleGetRawRequestResponseVehicle(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleGetRawRequestResponseVehicle(s) + + return nil +} + +func (e ExampleGetRawRequestResponseVehicle) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleGetRawRequestResponseVehicle) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleGetRawRequestResponseVehicle) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleGetRawRequestResponseVehicle.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleGetRawRequestResponseVehicle(s) + + return nil +} + +// ExampleGetRawRequestResponseAndHeadersVehicle +// Op: getRawRequestResponseAndHeaders Param: vehicle +type ExampleGetRawRequestResponseAndHeadersVehicle int8 + +const ( + UnknownExampleGetRawRequestResponseAndHeadersVehicle ExampleGetRawRequestResponseAndHeadersVehicle = iota + ExampleGetRawRequestResponseAndHeadersVehicleCar + ExampleGetRawRequestResponseAndHeadersVehicleTruck + ExampleGetRawRequestResponseAndHeadersVehicleBike +) + +func NewExampleGetRawRequestResponseAndHeadersVehicle(name string) ExampleGetRawRequestResponseAndHeadersVehicle { + switch name { + case "car": + return ExampleGetRawRequestResponseAndHeadersVehicleCar + case "truck": + return ExampleGetRawRequestResponseAndHeadersVehicleTruck + case "bike": + return ExampleGetRawRequestResponseAndHeadersVehicleBike + } + + return ExampleGetRawRequestResponseAndHeadersVehicle(0) +} + +var ExampleGetRawRequestResponseAndHeadersVehicleString = map[ExampleGetRawRequestResponseAndHeadersVehicle]string{ + ExampleGetRawRequestResponseAndHeadersVehicleCar: "car", + ExampleGetRawRequestResponseAndHeadersVehicleTruck: "truck", + ExampleGetRawRequestResponseAndHeadersVehicleBike: "bike", +} + +func (e ExampleGetRawRequestResponseAndHeadersVehicle) String() string { + return ExampleGetRawRequestResponseAndHeadersVehicleString[e] +} + +func (e *ExampleGetRawRequestResponseAndHeadersVehicle) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleGetRawRequestResponseAndHeadersVehicle(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleGetRawRequestResponseAndHeadersVehicle(s) + + return nil +} + +func (e ExampleGetRawRequestResponseAndHeadersVehicle) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleGetRawRequestResponseAndHeadersVehicle) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleGetRawRequestResponseAndHeadersVehicle) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleGetRawRequestResponseAndHeadersVehicle.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleGetRawRequestResponseAndHeadersVehicle(s) + + return nil +} + +// ExampleGetRawResponseVehicle +// Op: getRawResponse Param: vehicle +type ExampleGetRawResponseVehicle int8 + +const ( + UnknownExampleGetRawResponseVehicle ExampleGetRawResponseVehicle = iota + ExampleGetRawResponseVehicleCar + ExampleGetRawResponseVehicleTruck + ExampleGetRawResponseVehicleBike +) + +func NewExampleGetRawResponseVehicle(name string) ExampleGetRawResponseVehicle { + switch name { + case "car": + return ExampleGetRawResponseVehicleCar + case "truck": + return ExampleGetRawResponseVehicleTruck + case "bike": + return ExampleGetRawResponseVehicleBike + } + + return ExampleGetRawResponseVehicle(0) +} + +var ExampleGetRawResponseVehicleString = map[ExampleGetRawResponseVehicle]string{ + ExampleGetRawResponseVehicleCar: "car", + ExampleGetRawResponseVehicleTruck: "truck", + ExampleGetRawResponseVehicleBike: "bike", +} + +func (e ExampleGetRawResponseVehicle) String() string { + return ExampleGetRawResponseVehicleString[e] +} + +func (e *ExampleGetRawResponseVehicle) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleGetRawResponseVehicle(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleGetRawResponseVehicle(s) + + return nil +} + +func (e ExampleGetRawResponseVehicle) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleGetRawResponseVehicle) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleGetRawResponseVehicle) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleGetRawResponseVehicle.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleGetRawResponseVehicle(s) + + return nil +} + +// ExampleGetTestVehicle +// Op: getTest Param: vehicle +type ExampleGetTestVehicle int8 + +const ( + UnknownExampleGetTestVehicle ExampleGetTestVehicle = iota + ExampleGetTestVehicleCar + ExampleGetTestVehicleTruck + ExampleGetTestVehicleBike +) + +func NewExampleGetTestVehicle(name string) ExampleGetTestVehicle { + switch name { + case "car": + return ExampleGetTestVehicleCar + case "truck": + return ExampleGetTestVehicleTruck + case "bike": + return ExampleGetTestVehicleBike + } + + return ExampleGetTestVehicle(0) +} + +var ExampleGetTestVehicleString = map[ExampleGetTestVehicle]string{ + ExampleGetTestVehicleCar: "car", + ExampleGetTestVehicleTruck: "truck", + ExampleGetTestVehicleBike: "bike", +} + +func (e ExampleGetTestVehicle) String() string { + return ExampleGetTestVehicleString[e] +} + +func (e *ExampleGetTestVehicle) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleGetTestVehicle(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleGetTestVehicle(s) + + return nil +} + +func (e ExampleGetTestVehicle) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleGetTestVehicle) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleGetTestVehicle) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleGetTestVehicle.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleGetTestVehicle(s) + + return nil +} + +// ExampleGetTestVehicleDefault +// Op: getTest Param: vehicleDefault +type ExampleGetTestVehicleDefault int8 + +const ( + UnknownExampleGetTestVehicleDefault ExampleGetTestVehicleDefault = iota + ExampleGetTestVehicleDefaultCar + ExampleGetTestVehicleDefaultTruck + ExampleGetTestVehicleDefaultBike +) + +func NewExampleGetTestVehicleDefault(name string) ExampleGetTestVehicleDefault { + switch name { + case "car": + return ExampleGetTestVehicleDefaultCar + case "truck": + return ExampleGetTestVehicleDefaultTruck + case "bike": + return ExampleGetTestVehicleDefaultBike + } + + return ExampleGetTestVehicleDefault(0) +} + +var ExampleGetTestVehicleDefaultString = map[ExampleGetTestVehicleDefault]string{ + ExampleGetTestVehicleDefaultCar: "car", + ExampleGetTestVehicleDefaultTruck: "truck", + ExampleGetTestVehicleDefaultBike: "bike", +} + +func (e ExampleGetTestVehicleDefault) String() string { + return ExampleGetTestVehicleDefaultString[e] +} + +func (e *ExampleGetTestVehicleDefault) UnmarshalJSON(input []byte) (err error) { + var i int8 + + err = json.Unmarshal(input, &i) + if err == nil { + *e = ExampleGetTestVehicleDefault(i) + return nil + } + + var s string + + err = json.Unmarshal(input, &s) + if err != nil { + return err + } + + *e = NewExampleGetTestVehicleDefault(s) + + return nil +} + +func (e ExampleGetTestVehicleDefault) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e ExampleGetTestVehicleDefault) Value() (driver.Value, error) { + return json.Marshal(e.String()) +} + +func (e *ExampleGetTestVehicleDefault) Scan(src any) error { + s, ok := src.(string) + if !ok { + return fmt.Errorf("ExampleGetTestVehicleDefault.scan: scanned a %T, not []byte", src) //nolint + } + + *e = NewExampleGetTestVehicleDefault(s) + + return nil +} diff --git a/tests/example/foji.yaml b/tests/example/foji.yaml index cc0ef72..59c0079 100644 --- a/tests/example/foji.yaml +++ b/tests/example/foji.yaml @@ -14,6 +14,7 @@ processes: params: Package: foji/tests/example Auth: ExampleAuth + TypePrefix: Example OpenAPIFile: - tests/example/model_gen.go: foji/openapi/model.go.tpl + tests/example/client_model_gen.go: foji/openapi/model.go.tpl tests/example/http_client_gen.go: foji/openapi/client.go.tpl diff --git a/tests/example/http_client_gen.go b/tests/example/http_client_gen.go index 751fc46..6b1a0a1 100644 --- a/tests/example/http_client_gen.go +++ b/tests/example/http_client_gen.go @@ -20,32 +20,58 @@ import ( ) type ( - ClientAuthenticator = httputil.ClientAuthenticateFunc[*ExampleAuth] - ClientTokenAuthenticator = httputil.ClientTokenAuthenticatorFunc[*ExampleAuth] - ClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*ExampleAuth] - ClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*ExampleAuth] - ClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*ExampleAuth] - ClientSecurityGroup = httputil.ClientSecurityGroup[*ExampleAuth] - ClientSecurityGroups = httputil.ClientSecurityGroups[*ExampleAuth] + ExampleClientAuthenticator = httputil.ClientAuthenticateFunc[*ExampleAuth] + ExampleClientTokenAuthenticator = httputil.ClientTokenAuthenticatorFunc[*ExampleAuth] + ExampleClientBasicAuthenticator = httputil.ClientBasicAuthenticatorFunc[*ExampleAuth] + ExampleClientCookieAuthenticator = httputil.ClientCookieAuthenticatorFunc[*ExampleAuth] + ExampleClientWrappingAuthenticator = httputil.ClientWrappingAuthenticatorFunc[*ExampleAuth] + ExampleClientSecurityGroup = httputil.ClientSecurityGroup[*ExampleAuth] + ExampleClientSecurityGroups = httputil.ClientSecurityGroups[*ExampleAuth] ) -type Client struct { +type ExampleMethods interface { + GetExamples(ctx context.Context) (*ExampleExamples, error) + GetAuthComplex(ctx context.Context, user *ExampleAuth) error + GetAuthSimple(ctx context.Context, user *ExampleAuth) error + GetAuthSimpleMaybe(ctx context.Context, user *ExampleAuth) error + GetAuthSimple2(ctx context.Context, user *ExampleAuth) error + GetAuthSimple2Maybe(ctx context.Context, user *ExampleAuth) error + GetAuthComplexMaybe(ctx context.Context, user *ExampleAuth) error + GetComplexSecurity(ctx context.Context, user *ExampleAuth) ([]TestInt, error) + AddForm(ctx context.Context, body ExampleAddFormRequest) (*ExampleFooBar, error) + AddMultipartForm(ctx context.Context, body ExampleAddMultipartFormRequest) (*ExampleFooBar, error) + HeaderResponse(ctx context.Context) error + AddInlinedAllOf(ctx context.Context, body ExampleAddInlinedAllOfRequest) (*ExampleFooBar, error) + AddInlinedBody(ctx context.Context, body ExampleAddInlinedBodyRequest) (*ExampleFooBar, error) + GetExampleParams(ctx context.Context, k1 string, k2 uuid.UUID, k3 time.Time, k4 int32, k5 int64, enumTest ExampleGetExampleParamsEnumTest) (*ExampleExample, error) + NoResponse(ctx context.Context, body ExampleFoo) error + GetExampleOptional(ctx context.Context, k1 *string, k2 *uuid.UUID, k3 *time.Time, k4 *int32, k5 *int64, k5Default int64) (*ExampleExample, error) + GetExampleQuery(ctx context.Context, k1 string, k2 uuid.UUID, k3 time.Time, k4 int32, k5 int64, k6 []string, k7 []uuid.UUID) (*ExampleExample, error) + GetRawBody(ctx context.Context, body ExampleFoo) (*ExampleExample, error) + GetRawRequest(ctx context.Context, vehicle ExampleGetRawRequestVehicle) (*ExampleExample, error) + GetRawRequestResponse(ctx context.Context, vehicle ExampleGetRawRequestResponseVehicle) (*ExampleExample, error) + GetRawRequestResponseAndHeaders(ctx context.Context, vehicle ExampleGetRawRequestResponseAndHeadersVehicle) (*ExampleExample, error) + GetRawResponse(ctx context.Context, vehicle ExampleGetRawResponseVehicle) (*ExampleExample, error) + GetTest(ctx context.Context, vehicle ExampleGetTestVehicle, vehicleDefault ExampleGetTestVehicleDefault, playerID uuid.UUID, color ExampleColorQuery, colorDefault ExampleColorQueryDefault, season ExampleSeason) (*ExampleExample, error) +} + +type ExampleClient struct { baseURL string httpClient *http.Client - bearerAuth ClientAuthenticator - customHeaderAuthAuth ClientAuthenticator - headerAuthAuth ClientAuthenticator - jwtAuth ClientAuthenticator - rawAuth ClientAuthenticator - getAuthComplexSecurity ClientSecurityGroups - getAuthSimpleMaybeSecurity ClientSecurityGroups - getAuthSimple2MaybeSecurity ClientSecurityGroups - getAuthComplexMaybeSecurity ClientSecurityGroups - getComplexSecuritySecurity ClientSecurityGroups + bearerAuth ExampleClientAuthenticator + customHeaderAuthAuth ExampleClientAuthenticator + headerAuthAuth ExampleClientAuthenticator + jwtAuth ExampleClientAuthenticator + rawAuth ExampleClientAuthenticator + getAuthComplexSecurity ExampleClientSecurityGroups + getAuthSimpleMaybeSecurity ExampleClientSecurityGroups + getAuthSimple2MaybeSecurity ExampleClientSecurityGroups + getAuthComplexMaybeSecurity ExampleClientSecurityGroups + getComplexSecuritySecurity ExampleClientSecurityGroups } -func NewClient(baseURL string, httpClient *http.Client, bearerAuth ClientTokenAuthenticator, customHeaderAuthAuth ClientTokenAuthenticator, headerAuthAuth ClientTokenAuthenticator, jwtAuth ClientTokenAuthenticator, rawAuth ClientTokenAuthenticator) *Client { - c := &Client{ +func NewExampleClient(baseURL string, httpClient *http.Client, bearerAuth ExampleClientTokenAuthenticator, customHeaderAuthAuth ExampleClientTokenAuthenticator, headerAuthAuth ExampleClientTokenAuthenticator, jwtAuth ExampleClientTokenAuthenticator, rawAuth ExampleClientTokenAuthenticator) *ExampleClient { + c := &ExampleClient{ baseURL: baseURL, httpClient: httpClient, bearerAuth: httputil.BearerClientAuth("Authorization", bearerAuth), @@ -55,40 +81,40 @@ func NewClient(baseURL string, httpClient *http.Client, bearerAuth ClientTokenAu rawAuth: httputil.HeaderClientAuth("Authorization", rawAuth), } - c.getAuthComplexSecurity = ClientSecurityGroups{ - ClientSecurityGroup{c.headerAuthAuth}, - ClientSecurityGroup{c.headerAuthAuth}, - ClientSecurityGroup{c.jwtAuth}, + c.getAuthComplexSecurity = ExampleClientSecurityGroups{ + ExampleClientSecurityGroup{c.headerAuthAuth}, + ExampleClientSecurityGroup{c.headerAuthAuth}, + ExampleClientSecurityGroup{c.jwtAuth}, } - c.getAuthSimpleMaybeSecurity = ClientSecurityGroups{ - ClientSecurityGroup{c.headerAuthAuth}, - ClientSecurityGroup{}, + c.getAuthSimpleMaybeSecurity = ExampleClientSecurityGroups{ + ExampleClientSecurityGroup{c.headerAuthAuth}, + ExampleClientSecurityGroup{}, } - c.getAuthSimple2MaybeSecurity = ClientSecurityGroups{ - ClientSecurityGroup{c.headerAuthAuth}, - ClientSecurityGroup{c.headerAuthAuth}, - ClientSecurityGroup{}, + c.getAuthSimple2MaybeSecurity = ExampleClientSecurityGroups{ + ExampleClientSecurityGroup{c.headerAuthAuth}, + ExampleClientSecurityGroup{c.headerAuthAuth}, + ExampleClientSecurityGroup{}, } - c.getAuthComplexMaybeSecurity = ClientSecurityGroups{ - ClientSecurityGroup{c.headerAuthAuth}, - ClientSecurityGroup{c.jwtAuth}, - ClientSecurityGroup{}, + c.getAuthComplexMaybeSecurity = ExampleClientSecurityGroups{ + ExampleClientSecurityGroup{c.headerAuthAuth}, + ExampleClientSecurityGroup{c.jwtAuth}, + ExampleClientSecurityGroup{}, } - c.getComplexSecuritySecurity = ClientSecurityGroups{ - ClientSecurityGroup{c.rawAuth}, - ClientSecurityGroup{c.bearerAuth}, - ClientSecurityGroup{c.customHeaderAuthAuth}, + c.getComplexSecuritySecurity = ExampleClientSecurityGroups{ + ExampleClientSecurityGroup{c.rawAuth}, + ExampleClientSecurityGroup{c.bearerAuth}, + ExampleClientSecurityGroup{c.customHeaderAuthAuth}, } return c } // GetExamples -func (c *Client) GetExamples(ctx context.Context) (*Examples, error) { +func (c *ExampleClient) GetExamples(ctx context.Context) (*ExampleExamples, error) { u := c.baseURL + "/examples" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) @@ -110,7 +136,7 @@ func (c *Client) GetExamples(ctx context.Context) (*Examples, error) { return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out Examples + var out ExampleExamples if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -119,7 +145,7 @@ func (c *Client) GetExamples(ctx context.Context) (*Examples, error) { } // GetAuthComplex -func (c *Client) GetAuthComplex(ctx context.Context, user *ExampleAuth) error { +func (c *ExampleClient) GetAuthComplex(ctx context.Context, user *ExampleAuth) error { u := c.baseURL + "/examples/auth/complex" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) @@ -149,7 +175,7 @@ func (c *Client) GetAuthComplex(ctx context.Context, user *ExampleAuth) error { } // GetAuthSimple -func (c *Client) GetAuthSimple(ctx context.Context, user *ExampleAuth) error { +func (c *ExampleClient) GetAuthSimple(ctx context.Context, user *ExampleAuth) error { u := c.baseURL + "/examples/auth/simple" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) @@ -179,7 +205,7 @@ func (c *Client) GetAuthSimple(ctx context.Context, user *ExampleAuth) error { } // GetAuthSimpleMaybe -func (c *Client) GetAuthSimpleMaybe(ctx context.Context, user *ExampleAuth) error { +func (c *ExampleClient) GetAuthSimpleMaybe(ctx context.Context, user *ExampleAuth) error { u := c.baseURL + "/examples/auth/simple/maybe" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) @@ -209,7 +235,7 @@ func (c *Client) GetAuthSimpleMaybe(ctx context.Context, user *ExampleAuth) erro } // GetAuthSimple2 -func (c *Client) GetAuthSimple2(ctx context.Context, user *ExampleAuth) error { +func (c *ExampleClient) GetAuthSimple2(ctx context.Context, user *ExampleAuth) error { u := c.baseURL + "/examples/auth/simple2" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) @@ -239,7 +265,7 @@ func (c *Client) GetAuthSimple2(ctx context.Context, user *ExampleAuth) error { } // GetAuthSimple2Maybe -func (c *Client) GetAuthSimple2Maybe(ctx context.Context, user *ExampleAuth) error { +func (c *ExampleClient) GetAuthSimple2Maybe(ctx context.Context, user *ExampleAuth) error { u := c.baseURL + "/examples/auth/simple2/maybe" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) @@ -269,7 +295,7 @@ func (c *Client) GetAuthSimple2Maybe(ctx context.Context, user *ExampleAuth) err } // GetAuthComplexMaybe -func (c *Client) GetAuthComplexMaybe(ctx context.Context, user *ExampleAuth) error { +func (c *ExampleClient) GetAuthComplexMaybe(ctx context.Context, user *ExampleAuth) error { u := c.baseURL + "/examples/complexAuthMaybe" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) @@ -299,7 +325,7 @@ func (c *Client) GetAuthComplexMaybe(ctx context.Context, user *ExampleAuth) err } // GetComplexSecurity -func (c *Client) GetComplexSecurity(ctx context.Context, user *ExampleAuth) ([]TestInt, error) { +func (c *ExampleClient) GetComplexSecurity(ctx context.Context, user *ExampleAuth) ([]TestInt, error) { u := c.baseURL + "/examples/complexSecurity" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) @@ -334,7 +360,7 @@ func (c *Client) GetComplexSecurity(ctx context.Context, user *ExampleAuth) ([]T } // AddForm -func (c *Client) AddForm(ctx context.Context, body AddFormRequest) (*FooBar, error) { +func (c *ExampleClient) AddForm(ctx context.Context, body ExampleAddFormRequest) (*ExampleFooBar, error) { u := c.baseURL + "/examples/form" form := url.Values{} @@ -416,7 +442,7 @@ func (c *Client) AddForm(ctx context.Context, body AddFormRequest) (*FooBar, err return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out FooBar + var out ExampleFooBar if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -425,7 +451,7 @@ func (c *Client) AddForm(ctx context.Context, body AddFormRequest) (*FooBar, err } // AddMultipartForm -func (c *Client) AddMultipartForm(ctx context.Context, body AddMultipartFormRequest) (*FooBar, error) { +func (c *ExampleClient) AddMultipartForm(ctx context.Context, body ExampleAddMultipartFormRequest) (*ExampleFooBar, error) { u := c.baseURL + "/examples/form:multipart" var bodyBuf bytes.Buffer @@ -503,7 +529,7 @@ func (c *Client) AddMultipartForm(ctx context.Context, body AddMultipartFormRequ return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out FooBar + var out ExampleFooBar if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -513,7 +539,7 @@ func (c *Client) AddMultipartForm(ctx context.Context, body AddMultipartFormRequ // HeaderResponse // Check header responses -func (c *Client) HeaderResponse(ctx context.Context) error { +func (c *ExampleClient) HeaderResponse(ctx context.Context) error { u := c.baseURL + "/examples/header" req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody) @@ -539,7 +565,7 @@ func (c *Client) HeaderResponse(ctx context.Context) error { } // AddInlinedAllOf -func (c *Client) AddInlinedAllOf(ctx context.Context, body AddInlinedAllOfRequest) (*FooBar, error) { +func (c *ExampleClient) AddInlinedAllOf(ctx context.Context, body ExampleAddInlinedAllOfRequest) (*ExampleFooBar, error) { u := c.baseURL + "/examples/inlinedAllOf" buf, err := json.Marshal(body) @@ -571,7 +597,7 @@ func (c *Client) AddInlinedAllOf(ctx context.Context, body AddInlinedAllOfReques return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out FooBar + var out ExampleFooBar if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -580,7 +606,7 @@ func (c *Client) AddInlinedAllOf(ctx context.Context, body AddInlinedAllOfReques } // AddInlinedBody -func (c *Client) AddInlinedBody(ctx context.Context, body AddInlinedBodyRequest) (*FooBar, error) { +func (c *ExampleClient) AddInlinedBody(ctx context.Context, body ExampleAddInlinedBodyRequest) (*ExampleFooBar, error) { u := c.baseURL + "/examples/inlinedBody" buf, err := json.Marshal(body) @@ -612,7 +638,7 @@ func (c *Client) AddInlinedBody(ctx context.Context, body AddInlinedBodyRequest) return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out FooBar + var out ExampleFooBar if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -621,7 +647,7 @@ func (c *Client) AddInlinedBody(ctx context.Context, body AddInlinedBodyRequest) } // GetExampleParams -func (c *Client) GetExampleParams(ctx context.Context, k1 string, k2 uuid.UUID, k3 time.Time, k4 int32, k5 int64, enumTest GetExampleParamsEnumTest) (*Example, error) { +func (c *ExampleClient) GetExampleParams(ctx context.Context, k1 string, k2 uuid.UUID, k3 time.Time, k4 int32, k5 int64, enumTest ExampleGetExampleParamsEnumTest) (*ExampleExample, error) { u := c.baseURL + "/examples/key1/{k1}/key2/{k2}/key3/{k3}/key4/{key4}/key5/{key5}" queryParams := url.Values{} u = strings.Replace(u, "{k1}", url.PathEscape(k1), 1) @@ -654,7 +680,7 @@ func (c *Client) GetExampleParams(ctx context.Context, k1 string, k2 uuid.UUID, return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out Example + var out ExampleExample if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -663,7 +689,7 @@ func (c *Client) GetExampleParams(ctx context.Context, k1 string, k2 uuid.UUID, } // NoResponse -func (c *Client) NoResponse(ctx context.Context, body Foo) error { +func (c *ExampleClient) NoResponse(ctx context.Context, body ExampleFoo) error { u := c.baseURL + "/examples/noResponse" buf, err := json.Marshal(body) @@ -699,7 +725,7 @@ func (c *Client) NoResponse(ctx context.Context, body Foo) error { } // GetExampleOptional -func (c *Client) GetExampleOptional(ctx context.Context, k1 *string, k2 *uuid.UUID, k3 *time.Time, k4 *int32, k5 *int64, k5Default int64) (*Example, error) { +func (c *ExampleClient) GetExampleOptional(ctx context.Context, k1 *string, k2 *uuid.UUID, k3 *time.Time, k4 *int32, k5 *int64, k5Default int64) (*ExampleExample, error) { u := c.baseURL + "/examples/optional" queryParams := url.Values{} if k1 != nil { @@ -742,7 +768,7 @@ func (c *Client) GetExampleOptional(ctx context.Context, k1 *string, k2 *uuid.UU return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out Example + var out ExampleExample if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -751,7 +777,7 @@ func (c *Client) GetExampleOptional(ctx context.Context, k1 *string, k2 *uuid.UU } // GetExampleQuery -func (c *Client) GetExampleQuery(ctx context.Context, k1 string, k2 uuid.UUID, k3 time.Time, k4 int32, k5 int64, k6 []string, k7 []uuid.UUID) (*Example, error) { +func (c *ExampleClient) GetExampleQuery(ctx context.Context, k1 string, k2 uuid.UUID, k3 time.Time, k4 int32, k5 int64, k6 []string, k7 []uuid.UUID) (*ExampleExample, error) { u := c.baseURL + "/examples/query" queryParams := url.Values{} queryParams.Set("k1", k1) @@ -789,7 +815,7 @@ func (c *Client) GetExampleQuery(ctx context.Context, k1 string, k2 uuid.UUID, k return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out Example + var out ExampleExample if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -798,7 +824,7 @@ func (c *Client) GetExampleQuery(ctx context.Context, k1 string, k2 uuid.UUID, k } // GetRawBody -func (c *Client) GetRawBody(ctx context.Context, body Foo) (*Example, error) { +func (c *ExampleClient) GetRawBody(ctx context.Context, body ExampleFoo) (*ExampleExample, error) { u := c.baseURL + "/examples/rawBody" buf, err := json.Marshal(body) @@ -830,7 +856,7 @@ func (c *Client) GetRawBody(ctx context.Context, body Foo) (*Example, error) { return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out Example + var out ExampleExample if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -839,7 +865,7 @@ func (c *Client) GetRawBody(ctx context.Context, body Foo) (*Example, error) { } // GetRawRequest -func (c *Client) GetRawRequest(ctx context.Context, vehicle GetRawRequestVehicle) (*Example, error) { +func (c *ExampleClient) GetRawRequest(ctx context.Context, vehicle ExampleGetRawRequestVehicle) (*ExampleExample, error) { u := c.baseURL + "/examples/rawRequest" queryParams := url.Values{} queryParams.Set("vehicle", vehicle.String()) @@ -867,7 +893,7 @@ func (c *Client) GetRawRequest(ctx context.Context, vehicle GetRawRequestVehicle return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out Example + var out ExampleExample if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -876,7 +902,7 @@ func (c *Client) GetRawRequest(ctx context.Context, vehicle GetRawRequestVehicle } // GetRawRequestResponse -func (c *Client) GetRawRequestResponse(ctx context.Context, vehicle GetRawRequestResponseVehicle) (*Example, error) { +func (c *ExampleClient) GetRawRequestResponse(ctx context.Context, vehicle ExampleGetRawRequestResponseVehicle) (*ExampleExample, error) { u := c.baseURL + "/examples/rawRequestResponse" queryParams := url.Values{} queryParams.Set("vehicle", vehicle.String()) @@ -904,7 +930,7 @@ func (c *Client) GetRawRequestResponse(ctx context.Context, vehicle GetRawReques return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out Example + var out ExampleExample if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -913,7 +939,7 @@ func (c *Client) GetRawRequestResponse(ctx context.Context, vehicle GetRawReques } // GetRawRequestResponseAndHeaders -func (c *Client) GetRawRequestResponseAndHeaders(ctx context.Context, vehicle GetRawRequestResponseAndHeadersVehicle) (*Example, error) { +func (c *ExampleClient) GetRawRequestResponseAndHeaders(ctx context.Context, vehicle ExampleGetRawRequestResponseAndHeadersVehicle) (*ExampleExample, error) { u := c.baseURL + "/examples/rawRequestResponseAndHeaders" queryParams := url.Values{} queryParams.Set("vehicle", vehicle.String()) @@ -941,7 +967,7 @@ func (c *Client) GetRawRequestResponseAndHeaders(ctx context.Context, vehicle Ge return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out Example + var out ExampleExample if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -950,7 +976,7 @@ func (c *Client) GetRawRequestResponseAndHeaders(ctx context.Context, vehicle Ge } // GetRawResponse -func (c *Client) GetRawResponse(ctx context.Context, vehicle GetRawResponseVehicle) (*Example, error) { +func (c *ExampleClient) GetRawResponse(ctx context.Context, vehicle ExampleGetRawResponseVehicle) (*ExampleExample, error) { u := c.baseURL + "/examples/rawResponse" queryParams := url.Values{} queryParams.Set("vehicle", vehicle.String()) @@ -978,7 +1004,7 @@ func (c *Client) GetRawResponse(ctx context.Context, vehicle GetRawResponseVehic return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out Example + var out ExampleExample if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } @@ -987,7 +1013,7 @@ func (c *Client) GetRawResponse(ctx context.Context, vehicle GetRawResponseVehic } // GetTest -func (c *Client) GetTest(ctx context.Context, vehicle GetTestVehicle, vehicleDefault GetTestVehicleDefault, playerID uuid.UUID, color ColorQuery, colorDefault ColorQueryDefault, season Season) (*Example, error) { +func (c *ExampleClient) GetTest(ctx context.Context, vehicle ExampleGetTestVehicle, vehicleDefault ExampleGetTestVehicleDefault, playerID uuid.UUID, color ExampleColorQuery, colorDefault ExampleColorQueryDefault, season ExampleSeason) (*ExampleExample, error) { u := c.baseURL + "/examples/test" queryParams := url.Values{} queryParams.Set("vehicle", vehicle.String()) @@ -1020,7 +1046,7 @@ func (c *Client) GetTest(ctx context.Context, vehicle GetTestVehicle, vehicleDef return nil, httputil.UnexpectedResponseError{Resp: resp, URL: u, Body: errBody} } - var out Example + var out ExampleExample if err := httputil.GetJSONBody(resp.Body, &out); err != nil { return nil, err } diff --git a/tests/tests_main.go b/tests/tests_main.go index 605731d..55619ab 100644 --- a/tests/tests_main.go +++ b/tests/tests_main.go @@ -50,6 +50,8 @@ func clientRaw(r *http.Request, inner *http.Client, user *example.ExampleAuth) ( return inner, nil } +func isA[T any](_ T) {} + func main() { // Requires the generated Operations to match the Service layer var _ csvresponse.Operations = &csvresponse.Service{} @@ -60,10 +62,10 @@ func main() { var authOps auth.Operations = &auth.Service{} auth.RegisterHTTP(authOps, http.NewServeMux(), tokenAuth, tokenAuth, tokenAuth, basicAuth, tokenAuth, rawAuth, rawAuth, rawAuth, rawAuth, authorize) - // Requires the generated clients to construct with authenticators matching each security scheme. - _ = csvresponse.NewClient("http://localhost", http.DefaultClient, csvClientToken) - _ = example.NewClient("http://localhost", http.DefaultClient, clientToken, clientToken, clientToken, clientToken, clientToken) - _ = auth.NewClient("http://localhost", http.DefaultClient, clientCookie, clientToken, clientToken, clientBasic, clientToken, clientWrap, clientWrap, clientWrap, clientRaw) + // Requires the generated clients to construct with authenticators matching each security scheme, and meet the interface. + isA[csvresponse.Methods](csvresponse.NewClient("http://localhost", http.DefaultClient, csvClientToken)) + isA[example.ExampleMethods](example.NewExampleClient("http://localhost", http.DefaultClient, clientToken, clientToken, clientToken, clientToken, clientToken)) + isA[auth.Methods](auth.NewClient("http://localhost", http.DefaultClient, clientCookie, clientToken, clientToken, clientBasic, clientToken, clientWrap, clientWrap, clientWrap, clientRaw)) os.Exit(0) } From 9560c05556fb84f74315ccebcbdf943a7ba06b80 Mon Sep 17 00:00:00 2001 From: David Newgas Date: Fri, 17 Jul 2026 14:36:21 -0700 Subject: [PATCH 9/9] Add param to ignore x-go-type Some openapi specs use x-go-type, referring to types defined in the server. We don't want these when generating clients, so add a param to ignore them. This is true by default in the openAPIClient process --- cfg/config.go | 13 +++++++++++++ foji/foji.yaml | 2 ++ foji/openapi/model.go.tpl | 6 +++--- output/openapi.go | 2 +- tests/example/model_gen.go | 26 +++++++++++++------------- 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 1a5092e..1a06964 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -145,6 +145,19 @@ func (pp ParamMap) HasString(name string) (string, bool) { return s, ok } +// GetBool returns a bool param identified by `name`, otherwise false. +// Accepts either a bool or the string "true". +func (pp ParamMap) GetBool(name string) bool { + switch v := pp[name].(type) { + case bool: + return v + case string: + return v == "true" + } + + return false +} + // GetWithDefault returns a string param identified by `name`, otherwise returns the default. func (pp ParamMap) GetWithDefault(name, def string) string { p, ok := pp[name] diff --git a/foji/foji.yaml b/foji/foji.yaml index 50950f5..97fc936 100644 --- a/foji/foji.yaml +++ b/foji/foji.yaml @@ -101,6 +101,8 @@ processes: openAPIClient: format: go resources: [ api ] + params: + IgnoreGoType: true OpenAPIFile: 'models_gen.go': foji/openapi/model.go.tpl 'client_gen.go': foji/openapi/client.go.tpl diff --git a/foji/openapi/model.go.tpl b/foji/openapi/model.go.tpl index 073f599..8bf14a4 100644 --- a/foji/openapi/model.go.tpl +++ b/foji/openapi/model.go.tpl @@ -193,7 +193,7 @@ func (e *{{ $enumType }}) Scan(src any) error { {{- $key := .RuntimeParams.key }} {{- $label := .RuntimeParams.label }} -{{- if not ($.HasExtension $schema "x-go-type" )}} +{{- if or ($.Params.GetBool "IgnoreGoType") (not ($.HasExtension $schema "x-go-type" ))}} {{- $typeName := $.GetType $.PackageName $key $schema }} {{- $rawName := pascal $key }} {{- $declName := $.PrefixType $rawName }} @@ -277,7 +277,7 @@ func (p *{{ $declName }}) UnmarshalJSON(b []byte) error { var validationErrors validation.Errors {{ range $field, $schemaProp := ($.RequiredProperties $schema) }} if _, ok := requiredCheck["{{ $field }}"]; !ok { - validationErrors.Add("{{ $field }}", ErrMissingRequiredField) + validationErrors.Add("{{ $field }}", Err{{ $.Params.GetWithDefault "TypePrefix" "" }}MissingRequiredField) } {{ end }} @@ -508,7 +508,7 @@ import ( "github.com/bir/iken/validation" ) -var ErrMissingRequiredField = errors.New("missing required field") +var Err{{ $.Params.GetWithDefault "TypePrefix" "" }}MissingRequiredField = errors.New("missing required field") // Component Schemas diff --git a/output/openapi.go b/output/openapi.go index 433d062..171f14c 100644 --- a/output/openapi.go +++ b/output/openapi.go @@ -165,7 +165,7 @@ func (o *OpenAPIFileContext) GetType(currentPackage, name string, s *openapi3.Sc return "" } - if override, ok := s.Value.Extensions["x-go-type"]; ok { + if override, ok := s.Value.Extensions["x-go-type"]; ok && !o.Params.GetBool("IgnoreGoType") { return o.getXGoType(currentPackage, override) } diff --git a/tests/example/model_gen.go b/tests/example/model_gen.go index 3d15654..007b54c 100644 --- a/tests/example/model_gen.go +++ b/tests/example/model_gen.go @@ -17,7 +17,7 @@ import ( "github.com/google/uuid" ) -var ErrMissingRequiredField = errors.New("missing required field") +var ErrExampleMissingRequiredField = errors.New("missing required field") // Component Schemas @@ -158,7 +158,7 @@ func (p *Example) UnmarshalJSON(b []byte) error { var validationErrors validation.Errors if _, ok := requiredCheck["id"]; !ok { - validationErrors.Add("id", ErrMissingRequiredField) + validationErrors.Add("id", ErrExampleMissingRequiredField) } if validationErrors != nil { @@ -199,7 +199,7 @@ func (p *Examples) UnmarshalJSON(b []byte) error { var validationErrors validation.Errors if _, ok := requiredCheck["list"]; !ok { - validationErrors.Add("list", ErrMissingRequiredField) + validationErrors.Add("list", ErrExampleMissingRequiredField) } if validationErrors != nil { @@ -305,7 +305,7 @@ func (p *FooBar) UnmarshalJSON(b []byte) error { var validationErrors validation.Errors if _, ok := requiredCheck["a"]; !ok { - validationErrors.Add("a", ErrMissingRequiredField) + validationErrors.Add("a", ErrExampleMissingRequiredField) } if validationErrors != nil { @@ -762,23 +762,23 @@ func (p *Patterns) UnmarshalJSON(b []byte) error { var validationErrors validation.Errors if _, ok := requiredCheck["id"]; !ok { - validationErrors.Add("id", ErrMissingRequiredField) + validationErrors.Add("id", ErrExampleMissingRequiredField) } if _, ok := requiredCheck["stateAlways"]; !ok { - validationErrors.Add("stateAlways", ErrMissingRequiredField) + validationErrors.Add("stateAlways", ErrExampleMissingRequiredField) } if _, ok := requiredCheck["subObject"]; !ok { - validationErrors.Add("subObject", ErrMissingRequiredField) + validationErrors.Add("subObject", ErrExampleMissingRequiredField) } if _, ok := requiredCheck["subState"]; !ok { - validationErrors.Add("subState", ErrMissingRequiredField) + validationErrors.Add("subState", ErrExampleMissingRequiredField) } if _, ok := requiredCheck["timeStamp"]; !ok { - validationErrors.Add("timeStamp", ErrMissingRequiredField) + validationErrors.Add("timeStamp", ErrExampleMissingRequiredField) } if validationErrors != nil { @@ -1032,7 +1032,7 @@ func (p *PlayerAlways) UnmarshalJSON(b []byte) error { var validationErrors validation.Errors if _, ok := requiredCheck["id"]; !ok { - validationErrors.Add("id", ErrMissingRequiredField) + validationErrors.Add("id", ErrExampleMissingRequiredField) } if validationErrors != nil { @@ -1078,7 +1078,7 @@ func (p *PlayerMaybe) UnmarshalJSON(b []byte) error { var validationErrors validation.Errors if _, ok := requiredCheck["id"]; !ok { - validationErrors.Add("id", ErrMissingRequiredField) + validationErrors.Add("id", ErrExampleMissingRequiredField) } if validationErrors != nil { @@ -2872,11 +2872,11 @@ func (p *AddInlinedBodyRequest) UnmarshalJSON(b []byte) error { var validationErrors validation.Errors if _, ok := requiredCheck["f07"]; !ok { - validationErrors.Add("f07", ErrMissingRequiredField) + validationErrors.Add("f07", ErrExampleMissingRequiredField) } if _, ok := requiredCheck["f10"]; !ok { - validationErrors.Add("f10", ErrMissingRequiredField) + validationErrors.Add("f10", ErrExampleMissingRequiredField) } if validationErrors != nil {