diff --git a/README.md b/README.md index 1c38768..ca58e22 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,16 @@ When `nubx` is available, OASmith runs its pinned Oxfmt version through `nubx`'s local discovery and registry fallback. No Node project or installed Oxfmt dependency is required. Generation still works without `nubx`. +Generated clients use the first document-level server by default and the first +operation-level server for that operation. An explicit client base URL always +overrides either declaration. + +TypeScript clients emit JSON bodies, raw bodies as `BodyInit`, and fixed-length +ordered multipart bodies declared with `prefixItems` and `prefixEncoding`. +Binary multipart parts are `Blob` values; their media types must match the +content types declared by the corresponding prefix encoding. Unsupported +request-body shapes fail generation. + ## OpenTelemetry trace propagation Generated clients leave OpenTelemetry dependencies and SDK setup to the diff --git a/internal/tsemit/templates/api.ts.gotmpl b/internal/tsemit/templates/api.ts.gotmpl index 42a7fb2..69f3672 100644 --- a/internal/tsemit/templates/api.ts.gotmpl +++ b/internal/tsemit/templates/api.ts.gotmpl @@ -42,7 +42,31 @@ export class ResponseError extends Error { } } -type ResponseTimeout = { +{{if .HasMultipartBody}}function multipartContentTypeAllowed(value: string, allowedValues: string): boolean { + const [mediaType] = value.trim().toLowerCase().split(';', 1); + const [major, minor, extra] = mediaType.split('/'); + if (major === '' || minor === '' || extra !== undefined) { + return false; + } + for (const allowedValue of allowedValues.split(',')) { + const [allowedType] = allowedValue.trim().toLowerCase().split(';', 1); + const [allowedMajor, allowedMinor, allowedExtra] = allowedType.split('/'); + if ( + allowedExtra === undefined && + major === allowedMajor && + (minor === allowedMinor || allowedMinor === '*') + ) { + return true; + } + } + return false; +} + +function newMultipartBoundary(): string { + return 'oasmith-' + crypto.randomUUID(); +} + +{{end}}type ResponseTimeout = { signal: AbortSignal; stop(): void; }; @@ -513,6 +537,7 @@ async function runInterceptors( {{end}}export class DefaultApi { private baseURL: string; + private baseURLOverride: boolean; private fetch: typeof globalThis.fetch; private interceptors: FetchInterceptor[]; private responseTimeoutMs: number | undefined; @@ -524,7 +549,8 @@ async function runInterceptors( {{end}} constructor(options: ClientOptions = {}) { - this.baseURL = options.baseURL ?? ''; + this.baseURLOverride = options.baseURL !== undefined; + this.baseURL = options.baseURL ?? {{if .DefaultServerURL}}{{printf "%q" .DefaultServerURL}}{{else}}''{{end}}; this.fetch = options.fetch ?? globalThis.fetch; this.interceptors = options.interceptors ?? []; this.responseTimeoutMs = configuredTimeout( @@ -631,9 +657,25 @@ async function runInterceptors( throw new RequiredError('{{.Name}}', 'Required parameter "{{.Name}}" was null or undefined when calling {{$.ID}}().'); } -{{end}} const headerParameters: Record = {}; +{{end}}{{with .MultipartBody}}{{range .Parts}}{{if .Binary}} if (!multipartContentTypeAllowed(requestParameters['{{.Name}}'].type, {{printf "%q" .AllowedContentType}})) { + throw new TypeError('Multipart part {{.Name}} content type "' + requestParameters['{{.Name}}'].type + '" is not allowed'); + } + +{{end}}{{end}}{{end}} const headerParameters: Record = {}; {{if .HasSSE}} headerParameters['Accept'] = 'text/event-stream'; {{end}}{{if .BodyParamName}} headerParameters['Content-Type'] = 'application/json'; +{{end}}{{if .RawBodyParamName}} headerParameters['Content-Type'] = {{printf "%q" .RawBodyMediaType}}; +{{end}}{{with .MultipartBody}} const multipartBoundary = newMultipartBoundary(); + headerParameters['Content-Type'] = {{printf "%q" .MediaType}} + '; boundary=' + multipartBoundary; + const multipartBody = new Blob([ +{{range .Parts}} '--' + multipartBoundary + '\r\n', +{{if .Binary}} 'Content-Type: ' + requestParameters['{{.Name}}'].type + '\r\n\r\n', + requestParameters['{{.Name}}'], +{{else}} {{printf "%q" (printf "Content-Type: %s\r\n\r\n" .ContentType)}}, + JSON.stringify(requestParameters['{{.Name}}']), +{{end}} '\r\n', +{{end}} '--' + multipartBoundary + '--\r\n', + ]); {{end}}{{if .QueryParams}} const queryParameters = new URLSearchParams(); {{range .QueryParams}}{{if .Required}}{{if .Slice}} for (const value of requestParameters['{{.Name}}']) { queryParameters.append({{printf "%q" .WireName}}, String(value)); @@ -649,10 +691,13 @@ async function runInterceptors( {{end}} } } {{end}}{{end}} const queryString = queryParameters.toString(); -{{end}} return new Request(this.baseURL + {{.PathExpression}}{{if .QueryParams}} + (queryString === '' ? '' : '?' + queryString){{end}}, { +{{end}} const baseURL = {{if .ServerURL}}this.baseURLOverride ? this.baseURL : {{printf "%q" .ServerURL}}{{else}}this.baseURL{{end}}; + return new Request(baseURL + {{.PathExpression}}{{if .QueryParams}} + (queryString === '' ? '' : '?' + queryString){{end}}, { method: '{{.Method}}', headers: headerParameters, {{if .BodyParamName}} body: JSON.stringify(requestParameters['{{.BodyParamName}}']), +{{else if .RawBodyParamName}} body: requestParameters['{{.RawBodyParamName}}'], +{{else if .MultipartBody}} body: multipartBody, {{end}} }); } diff --git a/internal/tsemit/tsemit.go b/internal/tsemit/tsemit.go index 21229d9..052349a 100644 --- a/internal/tsemit/tsemit.go +++ b/internal/tsemit/tsemit.go @@ -187,9 +187,17 @@ func modelsTemplateData(doc *openapi.Document) modelsData { func apiSource(doc *openapi.Document) (string, error) { operations := doc.Operations() + for _, route := range operations { + if err := validateRequestBody(route.Operation); err != nil { + return "", err + } + } data := apiData{ Imports: modelImports(doc, operations), } + if len(doc.Servers) > 0 { + data.DefaultServerURL = strings.TrimRight(doc.Servers[0].URL, "/") + } for _, route := range operations { params := operationParams(route.Operation) if len(params) > 0 { @@ -202,6 +210,9 @@ func apiSource(doc *openapi.Document) (string, error) { if operation.HasSSE { data.HasSSE = true } + if operation.MultipartBody != nil { + data.HasMultipartBody = true + } data.Operations = append(data.Operations, operation) } return executeTemplate("api.ts", data) @@ -209,7 +220,9 @@ func apiSource(doc *openapi.Document) (string, error) { type apiData struct { Imports []string + DefaultServerURL string HasSSE bool + HasMultipartBody bool RequestInterfaces []requestInterfaceData Operations []operationData } @@ -222,6 +235,7 @@ type requestInterfaceData struct { type operationData struct { ID string Method string + ServerURL string Params []opParam RequiredParams []opParam Responses []opResponse @@ -235,16 +249,33 @@ type operationData struct { PositionalRequestObject string PathExpression string BodyParamName string + RawBodyParamName string + RawBodyMediaType string + MultipartBody *tsMultipartBodyData QueryParams []opParam HasSSE bool } +type tsMultipartBodyData struct { + MediaType string + Parts []tsMultipartPartData +} + +type tsMultipartPartData struct { + Name string + Type string + JSON bool + Binary bool + ContentType string + AllowedContentType string +} + func operationTemplateData(doc *openapi.Document, route openapi.OperationRoute) operationData { op := route.Operation params := operationParams(op) responses := operationResponses(doc, route.Method, op) data := operationData{ - ID: op.OperationID, + ID: openapi.LowerCamel(op.OperationID), Method: route.Method, Params: params, Responses: responses, @@ -258,6 +289,9 @@ func operationTemplateData(doc *openapi.Document, route openapi.OperationRoute) PathExpression: pathExpression(route.Path, params), HasSSE: doc.OperationHasSSEResponseMethod(route.Method, op), } + if len(op.Servers) > 0 { + data.ServerURL = strings.TrimRight(op.Servers[0].URL, "/") + } for _, param := range params { if param.Required { data.RequiredParams = append(data.RequiredParams, param) @@ -272,8 +306,15 @@ func operationTemplateData(doc *openapi.Document, route openapi.OperationRoute) } } if body := bodyParam(params); body != nil { - data.BodyParamName = body.Name + switch body.Kind { + case "body": + data.BodyParamName = body.Name + case "rawBody": + data.RawBodyParamName = body.Name + data.RawBodyMediaType, _ = op.RawRequestBodyMediaType() + } } + data.MultipartBody, _ = sequentialMultipartBody(op) return data } @@ -374,7 +415,17 @@ func operationParams(operation *openapi.Operation) []opParam { Slice: param.Schema != nil && param.Schema.IsArray(), }) } - if schema := operation.JSONRequestSchema(); schema != nil { + if multipartBody, ok := sequentialMultipartBody(operation); ok { + for _, part := range multipartBody.Parts { + params = append(params, opParam{ + Name: part.Name, + WireName: part.Name, + Type: part.Type, + Required: true, + Kind: "multipart", + }) + } + } else if schema := operation.JSONRequestSchema(); schema != nil { name := openapi.LowerCamel(openapi.RefName(schema.Ref)) if name == "" { name = "body" @@ -383,13 +434,93 @@ func operationParams(operation *openapi.Operation) []opParam { Name: name, WireName: "body", Type: tsType(schema), - Required: true, + Required: operation.RequestBody.Required, + Optional: optional(operation.RequestBody.Required), Kind: "body", }) + } else if _, ok := operation.RawRequestBodyMediaType(); ok { + params = append(params, opParam{ + Name: "body", + WireName: "body", + Type: "BodyInit", + Required: operation.RequestBody.Required, + Optional: optional(operation.RequestBody.Required), + Kind: "rawBody", + }) } return params } +func validateRequestBody(operation *openapi.Operation) error { + if operation.RequestBody == nil { + return nil + } + if media, ok := operation.RequestBody.Content["application/json"]; ok { + if media.Schema != nil { + return nil + } + return fmt.Errorf("operation %s has unsupported request body: application/json schema is missing", operation.OperationID) + } + if _, ok := sequentialMultipartBody(operation); ok { + return nil + } + if mediaType, ok := operation.RawRequestBodyMediaType(); ok && !strings.HasPrefix(mediaType, "multipart/") { + return nil + } + return fmt.Errorf("operation %s has unsupported request body", operation.OperationID) +} + +func sequentialMultipartBody(operation *openapi.Operation) (*tsMultipartBodyData, bool) { + if operation.RequestBody == nil { + return nil, false + } + mediaTypes := make([]string, 0, len(operation.RequestBody.Content)) + for mediaType := range operation.RequestBody.Content { + mediaTypes = append(mediaTypes, mediaType) + } + sort.Strings(mediaTypes) + for _, mediaType := range mediaTypes { + media := operation.RequestBody.Content[mediaType] + if !strings.HasPrefix(mediaType, "multipart/") || media.Schema == nil || + !media.Schema.Type.Has("array") || len(media.Schema.PrefixItems) != 2 || + len(media.PrefixEncoding) != 2 { + continue + } + minimum, maximum := 0, 0 + if media.Schema.MinItems != nil { + minimum = *media.Schema.MinItems + } + if media.Schema.MaxItems != nil { + maximum = *media.Schema.MaxItems + } + if minimum != 2 || maximum != 2 { + continue + } + body := &tsMultipartBodyData{MediaType: mediaType} + for index, schema := range media.Schema.PrefixItems { + name := openapi.LowerCamel(schema.Title) + if name == "" { + name = fmt.Sprintf("part%d", index+1) + } + part := tsMultipartPartData{ + Name: name, + ContentType: strings.TrimSpace(media.PrefixEncoding[index].ContentType), + } + if schema.Type.Has("string") && schema.Format == "binary" { + part.Binary = true + part.Type = "Blob" + part.AllowedContentType = part.ContentType + } else { + part.JSON = true + part.Type = tsType(schema) + } + body.Parts = append(body.Parts, part) + } + return body, true + } + return nil, false +} + func operationResponses(doc *openapi.Document, method string, operation *openapi.Operation) []opResponse { var statuses []string for status := range operation.Responses { @@ -649,7 +780,7 @@ func pathExpression(path string, params []opParam) string { func bodyParam(params []opParam) *opParam { for _, param := range params { - if param.Kind == "body" { + if param.Kind == "body" || param.Kind == "rawBody" { return ¶m } } diff --git a/oasmith_test.go b/oasmith_test.go index 73ae524..eba6b52 100644 --- a/oasmith_test.go +++ b/oasmith_test.go @@ -295,7 +295,8 @@ paths: func TestTypeScriptOperationIDCannotInventWireBehavior(t *testing.T) { t.Parallel() - for _, operationID := range []string{"uploadMedia", "youtubeVideosInsert"} { + var sources []string + for _, operationID := range []string{"uploadMedia", "youtube.videos.insert"} { doc, err := openapi.Parse([]byte(strings.ReplaceAll(`openapi: 3.2.0 info: title: TypeScript operation identity fixture @@ -328,6 +329,15 @@ paths: t.Fatalf("%s invented %q behavior:\n%s", operationID, forbidden, source) } } + sources = append(sources, source) + } + + normalizedDotted := strings.NewReplacer( + "YoutubeVideosInsert", "UploadMedia", + "youtubeVideosInsert", "uploadMedia", + ).Replace(sources[1]) + if sources[0] != normalizedDotted { + t.Fatal("changing only operationId changed generated wire behavior") } } @@ -409,6 +419,144 @@ components: } } +func TestTypeScriptClientGeneratesDeclaredSequentialMultipartAndServers(t *testing.T) { + t.Parallel() + + doc, err := openapi.Parse([]byte(`openapi: 3.2.0 +info: + title: YouTube upload fixture + version: "1" +servers: + - url: https://youtube.googleapis.com +paths: + /youtube/v3/videos: + get: + operationId: youtube.videos.list + responses: + "204": + description: Listed + /upload/youtube/v3/videos: + post: + operationId: youtube.videos.insert + servers: + - url: https://www.googleapis.com + parameters: + - name: part + in: query + required: true + schema: + type: string + - name: uploadType + in: query + required: true + schema: + type: string + const: multipart + requestBody: + required: true + content: + multipart/related: + schema: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - title: metadata + $ref: "#/components/schemas/Video" + - title: media + type: string + format: binary + prefixEncoding: + - contentType: application/json + - contentType: video/*,application/octet-stream + responses: + "201": + description: Uploaded + content: + application/json: + schema: + $ref: "#/components/schemas/Video" +components: + schemas: + Video: + type: object + required: [id] + properties: + id: + type: string +`)) + if err != nil { + t.Fatalf("parse TypeScript ordered multipart fixture: %v", err) + } + outDir := t.TempDir() + if err := tsemit.Emit(doc, tsemit.Options{OutDir: outDir}); err != nil { + t.Fatalf("emit TypeScript ordered multipart fixture: %v", err) + } + for name, source := range map[string]string{ + "multipart.test.ts": typescriptSequentialMultipartBehaviorTest, + "typecheck.ts": typescriptSequentialMultipartTypecheck, + } { + if err := os.WriteFile(filepath.Join(outDir, name), []byte(source), 0o644); err != nil { + t.Fatalf("write %s: %v", name, err) + } + } + cmd := exec.Command("nubx", "-y", "vitest@4.0.18", "run", "--globals", "--root", outDir, "multipart.test.ts") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("TypeScript ordered multipart behavior test failed: %v\n%s", err, output) + } + cmd = exec.Command( + "nubx", "-y", "-p", "typescript@5.9.2", "tsc", + "--noEmit", "--strict", "--target", "ES2022", "--module", "NodeNext", + "--moduleResolution", "NodeNext", "--allowImportingTsExtensions", + "--lib", "ES2022,DOM,DOM.Iterable", "typecheck.ts", + ) + cmd.Dir = outDir + output, err = cmd.CombinedOutput() + if err != nil { + t.Fatalf("generated TypeScript client typecheck failed: %v\n%s", err, output) + } + raw, err := os.ReadFile(filepath.Join(outDir, "api.ts")) + if err != nil { + t.Fatalf("read generated TypeScript multipart client: %v", err) + } + for _, forbidden := range []string{"Content-Range", "resumable", "StatusCode == 308", "Range header"} { + if strings.Contains(string(raw), forbidden) { + t.Fatalf("generated TypeScript multipart client invented %q behavior", forbidden) + } + } +} + +func TestTypeScriptClientRejectsUnsupportedRequestBody(t *testing.T) { + t.Parallel() + + doc, err := openapi.Parse([]byte(`openapi: 3.2.0 +info: + title: Unsupported request body fixture + version: "1" +paths: + /upload: + post: + operationId: upload + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + responses: + "204": + description: Uploaded +`)) + if err != nil { + t.Fatalf("parse unsupported request body fixture: %v", err) + } + err = tsemit.Emit(doc, tsemit.Options{OutDir: t.TempDir()}) + if err == nil || !strings.Contains(err.Error(), "operation upload has unsupported request body") { + t.Fatalf("unsupported request body error = %v", err) + } +} + func compareDirs(t *testing.T, goldenDir string, outDir string) { t.Helper() @@ -863,6 +1011,18 @@ void describe("TypeScript client queries", () => { assert.equal(url.searchParams.get("notify"), "true") assert.deepEqual(url.searchParams.getAll("label"), ["alpha beta", "x&y"]) }) + + void test("preserves declared raw request bodies", async () => { + const api = new DefaultApi({ baseURL: "https://example.test" }) + const request = api.uploadMediaRequest({ + owner: "channel/one", + uploadType: "media", + body: "complete-media", + }) + + assert.equal(request.headers.get("Content-Type"), "application/octet-stream") + assert.equal(await request.text(), "complete-media") + }) }) ` @@ -1463,3 +1623,86 @@ func TestDeclaredServerAndOrderedParts(t *testing.T) { } } ` + +const typescriptSequentialMultipartTypecheck = ` +import { DefaultApi, type YoutubeVideosInsertRequest } from './api.ts' + +const request: YoutubeVideosInsertRequest = { + part: 'snippet,status', + uploadType: 'multipart', + metadata: { id: 'metadata' }, + media: new Blob(['complete-media'], { type: 'video/mp4' }), +} + +new DefaultApi().youtubeVideosInsertRequest(request) +new DefaultApi().youtubeVideosListRequest() +` + +const typescriptSequentialMultipartBehaviorTest = ` +import { DefaultApi } from './api.ts' + +void describe('TypeScript declared multipart and servers', () => { + void test('uses declared servers and emits complete ordered parts', async () => { + function boundaryOf(request: Request): string { + const contentType = request.headers.get('Content-Type') ?? '' + const match = /^multipart\/related;\s*boundary=(.+)$/.exec(contentType) + if (match === null || match[1] === '') { + throw new Error('missing multipart/related boundary: ' + contentType) + } + return match[1] + } + + const api = new DefaultApi() + const request = api.youtubeVideosInsertRequest({ + part: 'snippet,status', + uploadType: 'multipart', + metadata: { id: 'metadata' }, + media: new Blob(['complete-media'], { type: 'video/mp4' }), + }) + const url = new URL(request.url) + expect(url.origin + url.pathname).toEqual( + 'https://www.googleapis.com/upload/youtube/v3/videos', + ) + expect(url.searchParams.get('part')).toEqual('snippet,status') + expect(url.searchParams.get('uploadType')).toEqual('multipart') + + const boundary = boundaryOf(request) + const body = await request.text() + expect(body).toEqual( + '--' + boundary + '\r\n' + + 'Content-Type: application/json\r\n\r\n' + + '{"id":"metadata"}\r\n' + + '--' + boundary + '\r\n' + + 'Content-Type: video/mp4\r\n\r\n' + + 'complete-media\r\n' + + '--' + boundary + '--\r\n', + ) + expect(api.youtubeVideosListRequest().url).toEqual( + 'https://youtube.googleapis.com/youtube/v3/videos', + ) + }) + + void test('keeps an explicit base URL authoritative', () => { + const api = new DefaultApi({ baseURL: 'https://fixture.example.test' }) + const request = api.youtubeVideosInsertRequest({ + part: 'snippet', + uploadType: 'multipart', + metadata: { id: 'metadata' }, + media: new Blob(['complete-media'], { type: 'application/octet-stream' }), + }) + expect(new URL(request.url).host).toEqual('fixture.example.test') + }) + + void test('rejects undeclared binary part content types', () => { + const api = new DefaultApi() + expect(() => + api.youtubeVideosInsertRequest({ + part: 'snippet', + uploadType: 'multipart', + metadata: { id: 'metadata' }, + media: new Blob(['complete-media'], { type: 'text/plain' }), + }), + ).toThrow(/media content type .* is not allowed/) + }) +}) +` diff --git a/testdata/golden/config-typescript/api.ts b/testdata/golden/config-typescript/api.ts index 9237d69..1d7d1c9 100644 --- a/testdata/golden/config-typescript/api.ts +++ b/testdata/golden/config-typescript/api.ts @@ -122,11 +122,13 @@ async function runInterceptors( export class DefaultApi { private baseURL: string + private baseURLOverride: boolean private fetch: typeof globalThis.fetch private interceptors: FetchInterceptor[] private responseTimeoutMs: number | undefined constructor(options: ClientOptions = {}) { + this.baseURLOverride = options.baseURL !== undefined this.baseURL = options.baseURL ?? "" this.fetch = options.fetch ?? globalThis.fetch this.interceptors = options.interceptors ?? [] diff --git a/testdata/golden/private-typescript/api.ts b/testdata/golden/private-typescript/api.ts index dc3563b..f0098c8 100644 --- a/testdata/golden/private-typescript/api.ts +++ b/testdata/golden/private-typescript/api.ts @@ -688,6 +688,7 @@ export interface RenderShowRSSFeedRequest { export class DefaultApi { private baseURL: string + private baseURLOverride: boolean private fetch: typeof globalThis.fetch private interceptors: FetchInterceptor[] private responseTimeoutMs: number | undefined @@ -698,6 +699,7 @@ export class DefaultApi { private sseReconnectOnStreamEnd: boolean constructor(options: ClientOptions = {}) { + this.baseURLOverride = options.baseURL !== undefined this.baseURL = options.baseURL ?? "" this.fetch = options.fetch ?? globalThis.fetch this.interceptors = options.interceptors ?? [] @@ -816,7 +818,8 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" - return new Request(this.baseURL + "/auth/email/verify", { + const baseURL = this.baseURL + return new Request(baseURL + "/auth/email/verify", { method: "POST", headers: headerParameters, body: JSON.stringify(requestParameters["emailVerification"]), @@ -886,7 +889,8 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" - return new Request(this.baseURL + "/auth/google/signup", { + const baseURL = this.baseURL + return new Request(baseURL + "/auth/google/signup", { method: "POST", headers: headerParameters, body: JSON.stringify(requestParameters["googleSignup"]), @@ -952,7 +956,8 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" - return new Request(this.baseURL + "/auth/login", { + const baseURL = this.baseURL + return new Request(baseURL + "/auth/login", { method: "POST", headers: headerParameters, body: JSON.stringify(requestParameters["passwordAuth"]), @@ -1026,7 +1031,8 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" - return new Request(this.baseURL + "/auth/password/reset/confirm", { + const baseURL = this.baseURL + return new Request(baseURL + "/auth/password/reset/confirm", { method: "POST", headers: headerParameters, body: JSON.stringify(requestParameters["passwordResetConfirmation"]), @@ -1096,7 +1102,8 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" - return new Request(this.baseURL + "/auth/password/reset/request", { + const baseURL = this.baseURL + return new Request(baseURL + "/auth/password/reset/request", { method: "POST", headers: headerParameters, body: JSON.stringify(requestParameters["passwordResetRequest"]), @@ -1158,7 +1165,8 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" - return new Request(this.baseURL + "/auth/signup", { + const baseURL = this.baseURL + return new Request(baseURL + "/auth/signup", { method: "POST", headers: headerParameters, body: JSON.stringify(requestParameters["passwordAuth"]), @@ -1214,7 +1222,8 @@ export class DefaultApi { whoamiRequest(): Request { const headerParameters: Record = {} - return new Request(this.baseURL + "/session", { + const baseURL = this.baseURL + return new Request(baseURL + "/session", { method: "GET", headers: headerParameters, }) @@ -1263,7 +1272,8 @@ export class DefaultApi { listAPIKeysRequest(): Request { const headerParameters: Record = {} - return new Request(this.baseURL + "/session/api-keys", { + const baseURL = this.baseURL + return new Request(baseURL + "/session/api-keys", { method: "GET", headers: headerParameters, }) @@ -1325,7 +1335,8 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" - return new Request(this.baseURL + "/session/api-keys", { + const baseURL = this.baseURL + return new Request(baseURL + "/session/api-keys", { method: "POST", headers: headerParameters, body: JSON.stringify(requestParameters["createAPIKey"]), @@ -1382,7 +1393,8 @@ export class DefaultApi { appShellRequest(): Request { const headerParameters: Record = {} - return new Request(this.baseURL + "/session/app-shell", { + const baseURL = this.baseURL + return new Request(baseURL + "/session/app-shell", { method: "GET", headers: headerParameters, }) @@ -1431,7 +1443,8 @@ export class DefaultApi { defaultTeamRequest(): Request { const headerParameters: Record = {} - return new Request(this.baseURL + "/session/default-team", { + const baseURL = this.baseURL + return new Request(baseURL + "/session/default-team", { method: "GET", headers: headerParameters, }) @@ -1494,8 +1507,9 @@ export class DefaultApi { } const headerParameters: Record = {} + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/pages/show/${encodeURIComponent(requestParameters["showId"])}`, { method: "GET", @@ -1565,8 +1579,9 @@ export class DefaultApi { } const headerParameters: Record = {} + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/pages/shows/${encodeURIComponent(requestParameters["teamId"])}`, { method: "GET", @@ -1637,7 +1652,8 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" - return new Request(this.baseURL + "/session/teams", { + const baseURL = this.baseURL + return new Request(baseURL + "/session/teams", { method: "POST", headers: headerParameters, body: JSON.stringify(requestParameters["createTeam"]), @@ -1714,8 +1730,9 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/teams/${encodeURIComponent(requestParameters["teamId"])}/image-uploads/presign`, { method: "POST", @@ -1803,8 +1820,9 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/teams/${encodeURIComponent(requestParameters["teamId"])}/media-uploads/multipart`, { method: "POST", @@ -1892,8 +1910,9 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/teams/${encodeURIComponent(requestParameters["teamId"])}/media-uploads/presign`, { method: "POST", @@ -1991,8 +2010,9 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/teams/${encodeURIComponent(requestParameters["teamId"])}/media-uploads/${encodeURIComponent(requestParameters["uploadSessionId"])}/complete`, { method: "POST", @@ -2091,8 +2111,9 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/teams/${encodeURIComponent(requestParameters["teamId"])}/media-uploads/${encodeURIComponent(requestParameters["uploadSessionId"])}/parts/presign`, { method: "POST", @@ -2168,8 +2189,9 @@ export class DefaultApi { } const headerParameters: Record = {} + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/teams/${encodeURIComponent(requestParameters["teamId"])}/shows`, { method: "GET", @@ -2246,8 +2268,9 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/teams/${encodeURIComponent(requestParameters["teamId"])}/shows`, { method: "POST", @@ -2347,8 +2370,9 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/teams/${encodeURIComponent(requestParameters["teamId"])}/shows/${encodeURIComponent(requestParameters["showId"])}`, { method: "PUT", @@ -2430,8 +2454,9 @@ export class DefaultApi { } const headerParameters: Record = {} + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/teams/${encodeURIComponent(requestParameters["teamId"])}/shows/${encodeURIComponent(requestParameters["showId"])}/episodes`, { method: "GET", @@ -2522,8 +2547,9 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/teams/${encodeURIComponent(requestParameters["teamId"])}/shows/${encodeURIComponent(requestParameters["showId"])}/episodes`, { method: "POST", @@ -2626,8 +2652,9 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Content-Type"] = "application/json" + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/teams/${encodeURIComponent(requestParameters["teamId"])}/shows/${encodeURIComponent(requestParameters["showId"])}/episodes/${encodeURIComponent(requestParameters["episodeId"])}`, { method: "PUT", @@ -2723,8 +2750,9 @@ export class DefaultApi { const headerParameters: Record = {} headerParameters["Accept"] = "text/event-stream" + const baseURL = this.baseURL return new Request( - this.baseURL + + baseURL + `/session/teams/${encodeURIComponent(requestParameters["teamId"])}/shows/${encodeURIComponent(requestParameters["showId"])}/episodes/${encodeURIComponent(requestParameters["episodeId"])}/episode-events`, { method: "GET", @@ -2828,7 +2856,8 @@ export class DefaultApi { listTestEmailsRequest(): Request { const headerParameters: Record = {} - return new Request(this.baseURL + "/test/emails", { + const baseURL = this.baseURL + return new Request(baseURL + "/test/emails", { method: "GET", headers: headerParameters, }) @@ -2891,8 +2920,9 @@ export class DefaultApi { } const headerParameters: Record = {} + const baseURL = this.baseURL return new Request( - this.baseURL + `/${encodeURIComponent(requestParameters["feedId"])}.rss`, + baseURL + `/${encodeURIComponent(requestParameters["feedId"])}.rss`, { method: "GET", headers: headerParameters,