From 06004c9999c33af7201cdd7e5ecf8c19bf4cd8f7 Mon Sep 17 00:00:00 2001 From: Toan Nguyen Date: Sat, 10 Aug 2024 00:03:30 +0700 Subject: [PATCH] support application/x-ndjson and replace default server variables (#30) --- openapi/internal/oas3.go | 12 +++- openapi/internal/oas3_operation.go | 27 ++++++-- openapi/testdata/openai/expected.json | 90 +++++++++++++++++++++++++++ openapi/testdata/openai/source.json | 67 ++++++++++++++++++++ schema/enum.go | 1 + 5 files changed, 192 insertions(+), 5 deletions(-) diff --git a/openapi/internal/oas3.go b/openapi/internal/oas3.go index 4d615dc..d0d3087 100644 --- a/openapi/internal/oas3.go +++ b/openapi/internal/oas3.go @@ -110,9 +110,19 @@ func (oc *OAS3Builder) convertServers(servers []*v3.Server) []rest.ServerConfig } } + serverURL := server.URL + for variable := server.Variables.First(); variable != nil; variable = variable.Next() { + value := variable.Value() + if value == nil || value.Default == "" { + continue + } + key := variable.Key() + serverURL = strings.ReplaceAll(serverURL, fmt.Sprintf("{%s}", key), value.Default) + } + conf := rest.ServerConfig{ ID: serverID, - URL: *rest.NewEnvStringTemplate(rest.NewEnvTemplateWithDefault(envName, server.URL)), + URL: *rest.NewEnvStringTemplate(rest.NewEnvTemplateWithDefault(envName, serverURL)), } results = append(results, conf) } diff --git a/openapi/internal/oas3_operation.go b/openapi/internal/oas3_operation.go index aba5841..dfd3cca 100644 --- a/openapi/internal/oas3_operation.go +++ b/openapi/internal/oas3_operation.go @@ -341,16 +341,35 @@ func (oc *oas3OperationBuilder) convertResponse(responses *v3.Responses, apiPath oc.builder.typeUsageCounter.Add(scalarName, 1) return schema.NewNullableNamedType(scalarName), nil } - jsonContent, ok := resp.Content.Get("application/json") - if !ok { + + var bodyContent *v3.MediaType + var present bool + var contentType string + for _, ct := range []string{rest.ContentTypeJSON, rest.ContentTypeNdJSON} { + bodyContent, present = resp.Content.Get(ct) + if present { + contentType = ct + break + } + } + + if !present { return nil, nil } schemaType, _, _, err := newOAS3SchemaBuilder(oc.builder, apiPath, rest.InBody, false). - getSchemaTypeFromProxy(jsonContent.Schema, false, fieldPaths) + getSchemaTypeFromProxy(bodyContent.Schema, false, fieldPaths) if err != nil { return nil, err } oc.builder.typeUsageCounter.Add(getNamedType(schemaType, true, ""), 1) - return schemaType, nil + + switch contentType { + case rest.ContentTypeNdJSON: + // Newline Delimited JSON (ndjson) format represents a stream of structured objects + // so the response would be wrapped with an array + return schema.NewArrayType(schemaType), nil + default: + return schemaType, nil + } } diff --git a/openapi/testdata/openai/expected.json b/openapi/testdata/openai/expected.json index dca0c3b..52d4579 100644 --- a/openapi/testdata/openai/expected.json +++ b/openapi/testdata/openai/expected.json @@ -3,6 +3,9 @@ "servers": [ { "url": "{{SERVER_URL:-https://api.openai.com/v1}}" + }, + { + "url": "{{SERVER_URL_2:-http://127.0.0.1:11434}}" } ], "timeout": "{{TIMEOUT}}", @@ -625,6 +628,20 @@ } } }, + "CreateModelRequest": { + "fields": { + "model": { + "description": "The name of the model to create", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + } + } + }, "FunctionObject": { "fields": { "description": { @@ -655,9 +672,82 @@ } } } + }, + "ProgressResponse": { + "fields": { + "completed": { + "description": "The completed size of the task", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + }, + "digest": { + "description": "The SHA256 digest of the blob", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "status": { + "description": "The status of the request", + "type": { + "type": "nullable", + "underlying_type": { + "name": "String", + "type": "named" + } + } + }, + "total": { + "description": "The total size of the task", + "type": { + "type": "nullable", + "underlying_type": { + "name": "Int32", + "type": "named" + } + } + } + } } }, "procedures": [ + { + "request": { + "url": "/api/create", + "method": "post", + "requestBody": { + "contentType": "application/json", + "schema": { + "type": "CreateModelRequest" + } + } + }, + "arguments": { + "body": { + "description": "Request body of POST /api/create", + "type": { + "name": "CreateModelRequest", + "type": "named" + } + } + }, + "name": "createModel", + "result_type": { + "element_type": { + "name": "ProgressResponse", + "type": "named" + }, + "type": "array" + } + }, { "request": { "url": "/chat/completions", diff --git a/openapi/testdata/openai/source.json b/openapi/testdata/openai/source.json index 61018cf..e448f1f 100644 --- a/openapi/testdata/openai/source.json +++ b/openapi/testdata/openai/source.json @@ -17,9 +17,46 @@ "servers": [ { "url": "https://api.openai.com/v1" + }, + { + "url": "http://{host}:{port}", + "variables": { + "host": { + "default": "127.0.0.1" + }, + "port": { + "default": "11434" + } + } } ], "paths": { + "/api/create": { + "post": { + "operationId": "createModel", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateModelRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/x-ndjson": { + "schema": { + "$ref": "#/components/schemas/ProgressResponse" + } + } + } + } + } + } + }, "/chat/completions": { "post": { "operationId": "createChatCompletion", @@ -994,6 +1031,36 @@ } }, "required": ["token", "logprob", "bytes", "top_logprobs"] + }, + "CreateModelRequest": { + "type": "object", + "properties": { + "model": { + "type": "string", + "description": "The name of the model to create" + } + } + }, + "ProgressResponse": { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "The status of the request" + }, + "digest": { + "type": "string", + "description": "The SHA256 digest of the blob" + }, + "total": { + "type": "integer", + "description": "The total size of the task" + }, + "completed": { + "type": "integer", + "description": "The completed size of the task" + } + } } } }, diff --git a/schema/enum.go b/schema/enum.go index 752b644..341674f 100644 --- a/schema/enum.go +++ b/schema/enum.go @@ -218,6 +218,7 @@ func IsDefaultScalar(name string) bool { const ( ContentTypeHeader = "Content-Type" ContentTypeJSON = "application/json" + ContentTypeNdJSON = "application/x-ndjson" ContentTypeXML = "application/xml" ContentTypeFormURLEncoded = "application/x-www-form-urlencoded" ContentTypeMultipartFormData = "multipart/form-data"