diff --git a/generators/php/codegen/src/context/PhpAttributeMapper.ts b/generators/php/codegen/src/context/PhpAttributeMapper.ts
index 16bd73dad2d..8f005ce02c7 100644
--- a/generators/php/codegen/src/context/PhpAttributeMapper.ts
+++ b/generators/php/codegen/src/context/PhpAttributeMapper.ts
@@ -74,13 +74,7 @@ export class PhpAttributeMapper {
});
}
- public getUnionTypeParameters({
- types,
- isOptional = false
- }: {
- types: php.Type[];
- isOptional?: boolean;
- }): php.AstNode[] {
+ public getUnionTypeParameters({ types, isOptional }: { types: php.Type[]; isOptional?: boolean }): php.AstNode[] {
const typeAttributeArguments = types.map((type) => this.getTypeAttributeArgument(type));
// remove duplicates, such as "string" and "string" if enums and strings are both in the union
return uniqWith([...typeAttributeArguments, ...(isOptional ? [php.codeblock("'null'")] : [])], isEqual);
diff --git a/generators/php/sdk/src/endpoint/http/HttpEndpointGenerator.ts b/generators/php/sdk/src/endpoint/http/HttpEndpointGenerator.ts
index 878f07c53be..92c54d43ee0 100644
--- a/generators/php/sdk/src/endpoint/http/HttpEndpointGenerator.ts
+++ b/generators/php/sdk/src/endpoint/http/HttpEndpointGenerator.ts
@@ -302,10 +302,7 @@ export class HttpEndpointGenerator extends AbstractEndpointGenerator {
arguments_: UnnamedArgument[];
types: php.Type[];
}): php.CodeBlock {
- const unionTypeParameters = this.context.phpAttributeMapper.getUnionTypeParameters({
- types,
- isOptional: false
- });
+ const unionTypeParameters = this.context.phpAttributeMapper.getUnionTypeParameters({ types });
// if deduping in getUnionTypeParameters results in one type, treat it like just that type
if (unionTypeParameters.length === 1) {
return this.decodeJsonResponse(types[0]);
diff --git a/seed/csharp-model/server-sent-events/.gitignore b/seed/csharp-model/server-sent-events/.gitignore
index 9965de29662..5e57f18055d 100644
--- a/seed/csharp-model/server-sent-events/.gitignore
+++ b/seed/csharp-model/server-sent-events/.gitignore
@@ -1,7 +1,10 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
-## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+## Get latest from `dotnet new gitignore`
+
+# dotenv files
+.env
# User-specific files
*.rsuser
@@ -399,6 +402,7 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
+.idea
##
## Visual studio for Mac
@@ -475,3 +479,6 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
+
+# Vim temporary swap files
+*.swp
diff --git a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents.Test/SeedServerSentEvents.Test.csproj b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents.Test/SeedServerSentEvents.Test.csproj
index 31335ba802f..7fa553ecb76 100644
--- a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents.Test/SeedServerSentEvents.Test.csproj
+++ b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents.Test/SeedServerSentEvents.Test.csproj
@@ -15,6 +15,8 @@
+
+
diff --git a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Completions/StreamedCompletion.cs b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Completions/StreamedCompletion.cs
index 108d0ed7e69..42b2117e3c2 100644
--- a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Completions/StreamedCompletion.cs
+++ b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Completions/StreamedCompletion.cs
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
+using SeedServerSentEvents.Core;
#nullable enable
@@ -11,4 +12,9 @@ public record StreamedCompletion
[JsonPropertyName("tokens")]
public int? Tokens { get; set; }
+
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
}
diff --git a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/CollectionItemSerializer.cs b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/CollectionItemSerializer.cs
index 46753a77c5a..98010900521 100644
--- a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/CollectionItemSerializer.cs
+++ b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/CollectionItemSerializer.cs
@@ -10,7 +10,7 @@ namespace SeedServerSentEvents.Core;
///
/// Type of item to convert.
/// Converter to use for individual items.
-public class CollectionItemSerializer
+internal class CollectionItemSerializer
: JsonConverter>
where TConverterType : JsonConverter
{
diff --git a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/Constants.cs b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/Constants.cs
new file mode 100644
index 00000000000..44b6ee86fcc
--- /dev/null
+++ b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/Constants.cs
@@ -0,0 +1,7 @@
+namespace SeedServerSentEvents.Core;
+
+internal static class Constants
+{
+ public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK";
+ public const string DateFormat = "yyyy-MM-dd";
+}
diff --git a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/DateTimeSerializer.cs b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/DateTimeSerializer.cs
new file mode 100644
index 00000000000..2dd35f4ec96
--- /dev/null
+++ b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/DateTimeSerializer.cs
@@ -0,0 +1,22 @@
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace SeedServerSentEvents.Core;
+
+internal class DateTimeSerializer : JsonConverter
+{
+ public override DateTime Read(
+ ref Utf8JsonReader reader,
+ System.Type typeToConvert,
+ JsonSerializerOptions options
+ )
+ {
+ return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind);
+ }
+
+ public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.ToString(Constants.DateTimeFormat));
+ }
+}
diff --git a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/JsonConfiguration.cs b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/JsonConfiguration.cs
new file mode 100644
index 00000000000..4e016a1bb3b
--- /dev/null
+++ b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/JsonConfiguration.cs
@@ -0,0 +1,32 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace SeedServerSentEvents.Core;
+
+internal static class JsonOptions
+{
+ public static readonly JsonSerializerOptions JsonSerializerOptions;
+
+ static JsonOptions()
+ {
+ JsonSerializerOptions = new JsonSerializerOptions
+ {
+ Converters = { new DateTimeSerializer(), new OneOfSerializer() },
+ WriteIndented = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ };
+ }
+}
+
+internal static class JsonUtils
+{
+ public static string Serialize(T obj)
+ {
+ return JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions);
+ }
+
+ public static T Deserialize(string json)
+ {
+ return JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!;
+ }
+}
diff --git a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/OneOfSerializer.cs b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/OneOfSerializer.cs
index fb6caa0f90f..71b9dbcc630 100644
--- a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/OneOfSerializer.cs
+++ b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/OneOfSerializer.cs
@@ -5,10 +5,9 @@
namespace SeedServerSentEvents.Core;
-public class OneOfSerializer : JsonConverter
- where TOneOf : IOneOf
+internal class OneOfSerializer : JsonConverter
{
- public override TOneOf? Read(
+ public override IOneOf? Read(
ref Utf8JsonReader reader,
System.Type typeToConvert,
JsonSerializerOptions options
@@ -17,14 +16,14 @@ JsonSerializerOptions options
if (reader.TokenType is JsonTokenType.Null)
return default;
- foreach (var (type, cast) in s_types)
+ foreach (var (type, cast) in GetOneOfTypes(typeToConvert))
{
try
{
var readerCopy = reader;
var result = JsonSerializer.Deserialize(ref readerCopy, type, options);
reader.Skip();
- return (TOneOf)cast.Invoke(null, [result])!;
+ return (IOneOf)cast.Invoke(null, [result])!;
}
catch (JsonException) { }
}
@@ -34,20 +33,18 @@ JsonSerializerOptions options
);
}
- private static readonly (System.Type type, MethodInfo cast)[] s_types = GetOneOfTypes();
-
- public override void Write(Utf8JsonWriter writer, TOneOf value, JsonSerializerOptions options)
+ public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value.Value, options);
}
- private static (System.Type type, MethodInfo cast)[] GetOneOfTypes()
+ private static (System.Type type, MethodInfo cast)[] GetOneOfTypes(System.Type typeToConvert)
{
- var casts = typeof(TOneOf)
+ var casts = typeToConvert
.GetRuntimeMethods()
.Where(m => m.IsSpecialName && m.Name == "op_Implicit")
.ToArray();
- var type = typeof(TOneOf);
+ var type = typeToConvert;
while (type != null)
{
if (
@@ -62,6 +59,11 @@ private static (System.Type type, MethodInfo cast)[] GetOneOfTypes()
type = type.BaseType;
}
- throw new InvalidOperationException($"{typeof(TOneOf)} isn't OneOf or OneOfBase");
+ throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase");
+ }
+
+ public override bool CanConvert(System.Type typeToConvert)
+ {
+ return typeof(IOneOf).IsAssignableFrom(typeToConvert);
}
}
diff --git a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/Public/Version.cs b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/Public/Version.cs
new file mode 100644
index 00000000000..8296b59ade5
--- /dev/null
+++ b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/Public/Version.cs
@@ -0,0 +1,6 @@
+namespace SeedServerSentEvents;
+
+internal class Version
+{
+ public const string Current = "0.0.1";
+}
diff --git a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/StringEnumSerializer.cs b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/StringEnumSerializer.cs
index 143654892f7..707a72f31b4 100644
--- a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/StringEnumSerializer.cs
+++ b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/Core/StringEnumSerializer.cs
@@ -4,7 +4,7 @@
namespace SeedServerSentEvents.Core;
-public class StringEnumSerializer : JsonConverter
+internal class StringEnumSerializer : JsonConverter
where TEnum : struct, System.Enum
{
private readonly Dictionary _enumToString = new();
diff --git a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/SeedServerSentEvents.csproj b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/SeedServerSentEvents.csproj
index 11f735b98cc..dfaed0b7562 100644
--- a/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/SeedServerSentEvents.csproj
+++ b/seed/csharp-model/server-sent-events/src/SeedServerSentEvents/SeedServerSentEvents.csproj
@@ -11,7 +11,7 @@
README.md
https://github.com/server-sent-events/fern
-
+
true
@@ -41,5 +41,10 @@
+
+
+ <_Parameter1>SeedServerSentEvents.Test
+
+
diff --git a/seed/csharp-sdk/server-sent-events/README.md b/seed/csharp-sdk/server-sent-events/README.md
new file mode 100644
index 00000000000..91c010b022f
--- /dev/null
+++ b/seed/csharp-sdk/server-sent-events/README.md
@@ -0,0 +1,87 @@
+# Seed C# Library
+
+[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FC%23)
+[![nuget shield](https://img.shields.io/nuget/v/SeedServerSentEvents)](https://nuget.org/packages/SeedServerSentEvents)
+
+The Seed C# library provides convenient access to the Seed API from C#.
+
+## Installation
+
+```sh
+nuget install SeedServerSentEvents
+```
+
+## Usage
+
+Instantiate and use the client with the following:
+
+```csharp
+using SeedServerSentEvents;
+
+var client = new SeedServerSentEventsClient();
+await client.Completions.StreamAsync(new StreamCompletionRequest { Query = "string" });
+```
+
+## Exception Handling
+
+When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error
+will be thrown.
+
+```csharp
+using SeedServerSentEvents;
+
+try {
+ var response = await client.Completions.StreamAsync(...);
+} catch (SeedServerSentEventsApiException e) {
+ System.Console.WriteLine(e.Body);
+ System.Console.WriteLine(e.StatusCode);
+}
+```
+
+## Advanced
+
+### Retries
+
+The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long
+as the request is deemed retriable and the number of retry attempts has not grown larger than the configured
+retry limit (default: 2).
+
+A request is deemed retriable when any of the following HTTP status codes is returned:
+
+- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout)
+- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests)
+- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors)
+
+Use the `MaxRetries` request option to configure this behavior.
+
+```csharp
+var response = await client.Completions.StreamAsync(
+ ...,
+ new RequestOptions {
+ MaxRetries: 0 // Override MaxRetries at the request level
+ }
+);
+```
+
+### Timeouts
+
+The SDK defaults to a 30 second timeout. Use the `Timeout` option to configure this behavior.
+
+```csharp
+var response = await client.Completions.StreamAsync(
+ ...,
+ new RequestOptions {
+ Timeout: TimeSpan.FromSeconds(3) // Override timeout to 3s
+ }
+);
+```
+
+## Contributing
+
+While we value open-source contributions to this SDK, this library is generated programmatically.
+Additions made directly to this library would have to be moved over to our generation code,
+otherwise they would be overwritten upon the next generated release. Feel free to open a PR as
+a proof of concept, but know that we will not be able to merge it as-is. We suggest opening
+an issue first to discuss with us!
+
+On the other hand, contributions to the README are always very welcome!
\ No newline at end of file
diff --git a/seed/csharp-sdk/server-sent-events/reference.md b/seed/csharp-sdk/server-sent-events/reference.md
new file mode 100644
index 00000000000..76ccc7a0adf
--- /dev/null
+++ b/seed/csharp-sdk/server-sent-events/reference.md
@@ -0,0 +1,41 @@
+# Reference
+## Completions
+client.Completions.StreamAsync(StreamCompletionRequest { ... })
+
+-
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```csharp
+await client.Completions.StreamAsync(new StreamCompletionRequest { Query = "string" });
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**request:** `StreamCompletionRequest`
+
+
+
+
+
+
+
+
+
+
diff --git a/seed/csharp-sdk/server-sent-events/snippet-templates.json b/seed/csharp-sdk/server-sent-events/snippet-templates.json
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/seed/csharp-sdk/server-sent-events/snippet.json b/seed/csharp-sdk/server-sent-events/snippet.json
new file mode 100644
index 00000000000..ad0e1d38051
--- /dev/null
+++ b/seed/csharp-sdk/server-sent-events/snippet.json
@@ -0,0 +1,17 @@
+{
+ "types": {},
+ "endpoints": [
+ {
+ "example_identifier": null,
+ "id": {
+ "path": "/stream",
+ "method": "POST",
+ "identifier_override": "endpoint_completions.stream"
+ },
+ "snippet": {
+ "type": "typescript",
+ "client": "using SeedServerSentEvents;\n\nvar client = new SeedServerSentEventsClient();\nawait client.Completions.StreamAsync(new StreamCompletionRequest { Query = \"string\" });\n"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/Core/RawClientTests.cs b/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/Core/RawClientTests.cs
new file mode 100644
index 00000000000..42f412c3b10
--- /dev/null
+++ b/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/Core/RawClientTests.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Net.Http;
+using FluentAssertions;
+using NUnit.Framework;
+using SeedServerSentEvents.Core;
+using WireMock.Server;
+using SystemTask = System.Threading.Tasks.Task;
+using WireMockRequest = WireMock.RequestBuilders.Request;
+using WireMockResponse = WireMock.ResponseBuilders.Response;
+
+namespace SeedServerSentEvents.Test.Core
+{
+ [TestFixture]
+ public class RawClientTests
+ {
+ private WireMockServer _server;
+ private HttpClient _httpClient;
+ private RawClient _rawClient;
+ private string _baseUrl;
+ private const int _maxRetries = 3;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _server = WireMockServer.Start();
+ _baseUrl = _server.Url ?? "";
+ _httpClient = new HttpClient { BaseAddress = new Uri(_baseUrl) };
+ _rawClient = new RawClient(
+ new ClientOptions() { HttpClient = _httpClient, MaxRetries = _maxRetries }
+ );
+ }
+
+ [Test]
+ [TestCase(408)]
+ [TestCase(429)]
+ [TestCase(500)]
+ [TestCase(504)]
+ public async SystemTask MakeRequestAsync_ShouldRetry_OnRetryableStatusCodes(int statusCode)
+ {
+ _server
+ .Given(WireMockRequest.Create().WithPath("/test").UsingGet())
+ .InScenario("Retry")
+ .WillSetStateTo("Server Error")
+ .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode));
+
+ _server
+ .Given(WireMockRequest.Create().WithPath("/test").UsingGet())
+ .InScenario("Retry")
+ .WhenStateIs("Server Error")
+ .WillSetStateTo("Success")
+ .RespondWith(WireMockResponse.Create().WithStatusCode(statusCode));
+
+ _server
+ .Given(WireMockRequest.Create().WithPath("/test").UsingGet())
+ .InScenario("Retry")
+ .WhenStateIs("Success")
+ .RespondWith(WireMockResponse.Create().WithStatusCode(200).WithBody("Success"));
+
+ var request = new RawClient.BaseApiRequest
+ {
+ BaseUrl = _baseUrl,
+ Method = HttpMethod.Get,
+ Path = "/test",
+ };
+
+ var response = await _rawClient.MakeRequestAsync(request);
+ Assert.That(response.StatusCode, Is.EqualTo(200));
+
+ var content = await response.Raw.Content.ReadAsStringAsync();
+ Assert.That(content, Is.EqualTo("Success"));
+
+ Assert.That(_server.LogEntries.Count, Is.EqualTo(_maxRetries));
+ }
+
+ [Test]
+ [TestCase(400)]
+ [TestCase(409)]
+ public async SystemTask MakeRequestAsync_ShouldRetry_OnNonRetryableStatusCodes(
+ int statusCode
+ )
+ {
+ _server
+ .Given(WireMockRequest.Create().WithPath("/test").UsingGet())
+ .InScenario("Retry")
+ .WillSetStateTo("Server Error")
+ .RespondWith(
+ WireMockResponse.Create().WithStatusCode(statusCode).WithBody("Failure")
+ );
+
+ var request = new RawClient.BaseApiRequest
+ {
+ BaseUrl = _baseUrl,
+ Method = HttpMethod.Get,
+ Path = "/test",
+ };
+
+ var response = await _rawClient.MakeRequestAsync(request);
+ Assert.That(response.StatusCode, Is.EqualTo(statusCode));
+
+ var content = await response.Raw.Content.ReadAsStringAsync();
+ Assert.That(content, Is.EqualTo("Failure"));
+
+ Assert.That(_server.LogEntries.Count, Is.EqualTo(1));
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ _server.Dispose();
+ _httpClient.Dispose();
+ }
+ }
+}
diff --git a/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/SeedServerSentEvents.Test.csproj b/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/SeedServerSentEvents.Test.csproj
index 5988c7f8c42..7fa553ecb76 100644
--- a/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/SeedServerSentEvents.Test.csproj
+++ b/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/SeedServerSentEvents.Test.csproj
@@ -16,6 +16,7 @@
+
diff --git a/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/Utils/JsonDiffChecker.cs b/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/Utils/JsonDiffChecker.cs
deleted file mode 100644
index 8542d7fd40c..00000000000
--- a/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/Utils/JsonDiffChecker.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using Newtonsoft.Json.Linq;
-using NUnit.Framework;
-
-namespace SeedServerSentEvents.Test.Utils;
-
-public static class JsonDiffChecker
-{
- public static void AssertJsonEquals(string jsonString1, string jsonString2)
- {
- var token1 = JToken.Parse(jsonString1);
- var token2 = JToken.Parse(jsonString2);
- var differences = GetJsonDifferences(token1, token2);
-
- Assert.That(
- differences,
- Is.Empty,
- $"The JSON strings are not equal: {string.Join(", ", differences)}"
- );
- }
-
- private static List GetJsonDifferences(JToken token1, JToken token2, string path = "")
- {
- var differences = new List();
-
- if (token1.Type != token2.Type)
- {
- differences.Add($"{path} has different types: {token1.Type} vs {token2.Type}");
- return differences;
- }
-
- if (token1 is JObject obj1 && token2 is JObject obj2)
- {
- foreach (var property in obj1.Properties())
- {
- var newPath = string.IsNullOrEmpty(path)
- ? property.Name
- : $"{path}.{property.Name}";
- if (!obj2.TryGetValue(property.Name, out JToken token2Value))
- {
- differences.Add($"{newPath} is missing in the second JSON");
- }
- else
- {
- differences.AddRange(GetJsonDifferences(property.Value, token2Value, newPath));
- }
- }
-
- foreach (var property in obj2.Properties())
- {
- var newPath = string.IsNullOrEmpty(path)
- ? property.Name
- : $"{path}.{property.Name}";
- if (!obj1.TryGetValue(property.Name, out _))
- {
- differences.Add($"{newPath} is missing in the first JSON");
- }
- }
- }
- else if (token1 is JArray array1 && token2 is JArray array2)
- {
- for (var i = 0; i < Math.Max(array1.Count, array2.Count); i++)
- {
- var newPath = $"{path}[{i}]";
- if (i >= array1.Count)
- {
- differences.Add($"{newPath} is missing in the first JSON");
- }
- else if (i >= array2.Count)
- {
- differences.Add($"{newPath} is missing in the second JSON");
- }
- else
- {
- differences.AddRange(GetJsonDifferences(array1[i], array2[i], newPath));
- }
- }
- }
- else if (!JToken.DeepEquals(token1, token2))
- {
- differences.Add($"{path} has different values: {token1} vs {token2}");
- }
-
- return differences;
- }
-}
diff --git a/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/Wire/BaseWireTest.cs b/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/Wire/BaseWireTest.cs
deleted file mode 100644
index 73cd5c96c11..00000000000
--- a/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents.Test/Wire/BaseWireTest.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using NUnit.Framework;
-using SeedServerSentEvents;
-using SeedServerSentEvents.Core;
-using WireMock.Logging;
-using WireMock.Server;
-using WireMock.Settings;
-
-#nullable enable
-
-namespace SeedServerSentEvents.Test.Wire;
-
-[SetUpFixture]
-public class BaseWireTest
-{
- protected static WireMockServer Server { get; set; } = null!;
-
- protected static SeedServerSentEventsClient Client { get; set; } = null!;
-
- [OneTimeSetUp]
- public void GlobalSetup()
- {
- // Start the WireMock server
- Server = WireMockServer.Start(
- new WireMockServerSettings { Logger = new WireMockConsoleLogger() }
- );
-
- // Initialize the Client
- Client = new SeedServerSentEventsClient(new ClientOptions { BaseUrl = Server.Urls[0] });
- }
-
- [OneTimeTearDown]
- public void GlobalTeardown()
- {
- Server.Stop();
- }
-}
diff --git a/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents/Completions/CompletionsClient.cs b/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents/Completions/CompletionsClient.cs
index 67703a6bded..19c515299ed 100644
--- a/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents/Completions/CompletionsClient.cs
+++ b/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents/Completions/CompletionsClient.cs
@@ -1,21 +1,31 @@
using System.Net.Http;
-using SeedServerSentEvents;
+using System.Threading;
+using System.Threading.Tasks;
using SeedServerSentEvents.Core;
#nullable enable
namespace SeedServerSentEvents;
-public class CompletionsClient
+public partial class CompletionsClient
{
private RawClient _client;
- public CompletionsClient(RawClient client)
+ internal CompletionsClient(RawClient client)
{
_client = client;
}
- public async Task StreamAsync(StreamCompletionRequest request, RequestOptions? options = null)
+ ///
+ ///
+ /// await client.Completions.StreamAsync(new StreamCompletionRequest { Query = "string" });
+ ///
+ ///
+ public async Task StreamAsync(
+ StreamCompletionRequest request,
+ RequestOptions? options = null,
+ CancellationToken cancellationToken = default
+ )
{
var response = await _client.MakeRequestAsync(
new RawClient.JsonApiRequest
@@ -24,14 +34,15 @@ public async Task StreamAsync(StreamCompletionRequest request, RequestOptions? o
Method = HttpMethod.Post,
Path = "stream",
Body = request,
- Options = options
- }
+ Options = options,
+ },
+ cancellationToken
);
var responseBody = await response.Raw.Content.ReadAsStringAsync();
throw new SeedServerSentEventsApiException(
$"Error with status code {response.StatusCode}",
response.StatusCode,
- JsonUtils.Deserialize
+
+
+ <_Parameter1>SeedServerSentEvents.Test
+
+
diff --git a/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents/SeedServerSentEventsClient.cs b/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents/SeedServerSentEventsClient.cs
index e91de74227f..09c8e604565 100644
--- a/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents/SeedServerSentEventsClient.cs
+++ b/seed/csharp-sdk/server-sent-events/src/SeedServerSentEvents/SeedServerSentEventsClient.cs
@@ -1,5 +1,3 @@
-using System;
-using SeedServerSentEvents;
using SeedServerSentEvents.Core;
#nullable enable
@@ -12,11 +10,24 @@ public partial class SeedServerSentEventsClient
public SeedServerSentEventsClient(ClientOptions? clientOptions = null)
{
- _client = new RawClient(
- new Dictionary() { { "X-Fern-Language", "C#" }, },
- new Dictionary>() { },
- clientOptions ?? new ClientOptions()
+ var defaultHeaders = new Headers(
+ new Dictionary()
+ {
+ { "X-Fern-Language", "C#" },
+ { "X-Fern-SDK-Name", "SeedServerSentEvents" },
+ { "X-Fern-SDK-Version", Version.Current },
+ { "User-Agent", "Fernserver-sent-events/0.0.1" },
+ }
);
+ clientOptions ??= new ClientOptions();
+ foreach (var header in defaultHeaders)
+ {
+ if (!clientOptions.Headers.ContainsKey(header.Key))
+ {
+ clientOptions.Headers[header.Key] = header.Value;
+ }
+ }
+ _client = new RawClient(clientOptions);
Completions = new CompletionsClient(_client);
}
diff --git a/seed/fastapi/seed.yml b/seed/fastapi/seed.yml
index fa67bbbf2ac..8ed825b7ee4 100644
--- a/seed/fastapi/seed.yml
+++ b/seed/fastapi/seed.yml
@@ -67,6 +67,7 @@ allowedFailures:
- websocket
- enum
- server-sent-events
+ - server-sent-event-examples
- streaming-parameter
- any-auth
# Complex circular refs
diff --git a/seed/java-model/server-sent-events/src/main/java/com/seed/serverSentEvents/model/completions/StreamedCompletion.java b/seed/java-model/server-sent-events/src/main/java/com/seed/serverSentEvents/model/completions/StreamedCompletion.java
index 0a05329a4a7..b271add6f6f 100644
--- a/seed/java-model/server-sent-events/src/main/java/com/seed/serverSentEvents/model/completions/StreamedCompletion.java
+++ b/seed/java-model/server-sent-events/src/main/java/com/seed/serverSentEvents/model/completions/StreamedCompletion.java
@@ -91,7 +91,7 @@ public Builder from(StreamedCompletion other) {
@java.lang.Override
@JsonSetter("delta")
public _FinalStage delta(String delta) {
- this.delta = delta;
+ this.delta = Objects.requireNonNull(delta, "delta must not be null");
return this;
}
diff --git a/seed/java-sdk/server-sent-events/snippet-templates.json b/seed/java-sdk/server-sent-events/snippet-templates.json
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/seed/java-sdk/server-sent-events/snippet.json b/seed/java-sdk/server-sent-events/snippet.json
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/core/FileStream.java b/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/core/FileStream.java
new file mode 100644
index 00000000000..cf1177b976d
--- /dev/null
+++ b/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/core/FileStream.java
@@ -0,0 +1,60 @@
+/**
+ * This file was auto-generated by Fern from our API Definition.
+ */
+package com.seed.serverSentEvents.core;
+
+import java.io.InputStream;
+import java.util.Objects;
+import okhttp3.MediaType;
+import okhttp3.RequestBody;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Represents a file stream with associated metadata for file uploads.
+ */
+public class FileStream {
+ private final InputStream inputStream;
+ private final String fileName;
+ private final MediaType contentType;
+
+ /**
+ * Constructs a FileStream with the given input stream and optional metadata.
+ *
+ * @param inputStream The input stream of the file content. Must not be null.
+ * @param fileName The name of the file, or null if unknown.
+ * @param contentType The MIME type of the file content, or null if unknown.
+ * @throws NullPointerException if inputStream is null
+ */
+ public FileStream(InputStream inputStream, @Nullable String fileName, @Nullable MediaType contentType) {
+ this.inputStream = Objects.requireNonNull(inputStream, "Input stream cannot be null");
+ this.fileName = fileName;
+ this.contentType = contentType;
+ }
+
+ public FileStream(InputStream inputStream) {
+ this(inputStream, null, null);
+ }
+
+ public InputStream getInputStream() {
+ return inputStream;
+ }
+
+ @Nullable
+ public String getFileName() {
+ return fileName;
+ }
+
+ @Nullable
+ public MediaType getContentType() {
+ return contentType;
+ }
+
+ /**
+ * Creates a RequestBody suitable for use with OkHttp client.
+ *
+ * @return A RequestBody instance representing this file stream.
+ */
+ public RequestBody toRequestBody() {
+ return new InputStreamRequestBody(contentType, inputStream);
+ }
+}
diff --git a/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/core/InputStreamRequestBody.java b/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/core/InputStreamRequestBody.java
new file mode 100644
index 00000000000..a97c3290df6
--- /dev/null
+++ b/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/core/InputStreamRequestBody.java
@@ -0,0 +1,79 @@
+/**
+ * This file was auto-generated by Fern from our API Definition.
+ */
+package com.seed.serverSentEvents.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+import okhttp3.MediaType;
+import okhttp3.RequestBody;
+import okhttp3.internal.Util;
+import okio.BufferedSink;
+import okio.Okio;
+import okio.Source;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * A custom implementation of OkHttp's RequestBody that wraps an InputStream.
+ * This class allows streaming of data from an InputStream directly to an HTTP request body,
+ * which is useful for file uploads or sending large amounts of data without loading it all into memory.
+ */
+public class InputStreamRequestBody extends RequestBody {
+ private final InputStream inputStream;
+ private final MediaType contentType;
+
+ /**
+ * Constructs an InputStreamRequestBody with the specified content type and input stream.
+ *
+ * @param contentType the MediaType of the content, or null if not known
+ * @param inputStream the InputStream containing the data to be sent
+ * @throws NullPointerException if inputStream is null
+ */
+ public InputStreamRequestBody(@Nullable MediaType contentType, InputStream inputStream) {
+ this.contentType = contentType;
+ this.inputStream = Objects.requireNonNull(inputStream, "inputStream == null");
+ }
+
+ /**
+ * Returns the content type of this request body.
+ *
+ * @return the MediaType of the content, or null if not specified
+ */
+ @Nullable
+ @Override
+ public MediaType contentType() {
+ return contentType;
+ }
+
+ /**
+ * Returns the content length of this request body, if known.
+ * This method attempts to determine the length using the InputStream's available() method,
+ * which may not always accurately reflect the total length of the stream.
+ *
+ * @return the content length, or -1 if the length is unknown
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public long contentLength() throws IOException {
+ return inputStream.available() == 0 ? -1 : inputStream.available();
+ }
+
+ /**
+ * Writes the content of the InputStream to the given BufferedSink.
+ * This method is responsible for transferring the data from the InputStream to the network request.
+ *
+ * @param sink the BufferedSink to write the content to
+ * @throws IOException if an I/O error occurs during writing
+ */
+ @Override
+ public void writeTo(BufferedSink sink) throws IOException {
+ Source source = null;
+ try {
+ source = Okio.source(inputStream);
+ sink.writeAll(source);
+ } finally {
+ Util.closeQuietly(Objects.requireNonNull(source));
+ }
+ }
+}
diff --git a/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/resources/completions/requests/StreamCompletionRequest.java b/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/resources/completions/requests/StreamCompletionRequest.java
index d05a1da4881..a27c8e22024 100644
--- a/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/resources/completions/requests/StreamCompletionRequest.java
+++ b/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/resources/completions/requests/StreamCompletionRequest.java
@@ -14,6 +14,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import org.jetbrains.annotations.NotNull;
@JsonInclude(JsonInclude.Include.NON_ABSENT)
@JsonDeserialize(builder = StreamCompletionRequest.Builder.class)
@@ -62,7 +63,7 @@ public static QueryStage builder() {
}
public interface QueryStage {
- _FinalStage query(String query);
+ _FinalStage query(@NotNull String query);
Builder from(StreamCompletionRequest other);
}
@@ -88,8 +89,8 @@ public Builder from(StreamCompletionRequest other) {
@java.lang.Override
@JsonSetter("query")
- public _FinalStage query(String query) {
- this.query = query;
+ public _FinalStage query(@NotNull String query) {
+ this.query = Objects.requireNonNull(query, "query must not be null");
return this;
}
diff --git a/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/resources/completions/types/StreamedCompletion.java b/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/resources/completions/types/StreamedCompletion.java
index 11e6253dcad..cf21a4b65e4 100644
--- a/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/resources/completions/types/StreamedCompletion.java
+++ b/seed/java-sdk/server-sent-events/src/main/java/com/seed/serverSentEvents/resources/completions/types/StreamedCompletion.java
@@ -16,6 +16,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import org.jetbrains.annotations.NotNull;
@JsonInclude(JsonInclude.Include.NON_ABSENT)
@JsonDeserialize(builder = StreamedCompletion.Builder.class)
@@ -72,7 +73,7 @@ public static DeltaStage builder() {
}
public interface DeltaStage {
- _FinalStage delta(String delta);
+ _FinalStage delta(@NotNull String delta);
Builder from(StreamedCompletion other);
}
@@ -105,8 +106,8 @@ public Builder from(StreamedCompletion other) {
@java.lang.Override
@JsonSetter("delta")
- public _FinalStage delta(String delta) {
- this.delta = delta;
+ public _FinalStage delta(@NotNull String delta) {
+ this.delta = Objects.requireNonNull(delta, "delta must not be null");
return this;
}
diff --git a/seed/java-spring/seed.yml b/seed/java-spring/seed.yml
index 752c958045e..6ff96320949 100644
--- a/seed/java-spring/seed.yml
+++ b/seed/java-spring/seed.yml
@@ -46,5 +46,6 @@ allowedFailures:
- response-property
- streaming
- server-sent-events
+ - server-sent-event-examples
- alias-extends
- any-auth
diff --git a/seed/php-model/grpc-proto-exhaustive/src/Column.php b/seed/php-model/grpc-proto-exhaustive/src/Column.php
index 2707a6bbb1a..574db314c45 100644
--- a/seed/php-model/grpc-proto-exhaustive/src/Column.php
+++ b/seed/php-model/grpc-proto-exhaustive/src/Column.php
@@ -24,7 +24,7 @@ class Column extends JsonSerializableType
/**
* @var array|array|null $metadata
*/
- #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $metadata;
/**
diff --git a/seed/php-model/grpc-proto-exhaustive/src/QueryColumn.php b/seed/php-model/grpc-proto-exhaustive/src/QueryColumn.php
index f23e5bf33cb..68edbdae71b 100644
--- a/seed/php-model/grpc-proto-exhaustive/src/QueryColumn.php
+++ b/seed/php-model/grpc-proto-exhaustive/src/QueryColumn.php
@@ -30,7 +30,7 @@ class QueryColumn extends JsonSerializableType
/**
* @var array|array|null $filter
*/
- #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $filter;
/**
diff --git a/seed/php-model/grpc-proto-exhaustive/src/ScoredColumn.php b/seed/php-model/grpc-proto-exhaustive/src/ScoredColumn.php
index 1d89380be18..6b6493d3c40 100644
--- a/seed/php-model/grpc-proto-exhaustive/src/ScoredColumn.php
+++ b/seed/php-model/grpc-proto-exhaustive/src/ScoredColumn.php
@@ -30,7 +30,7 @@ class ScoredColumn extends JsonSerializableType
/**
* @var array|array|null $metadata
*/
- #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $metadata;
/**
diff --git a/seed/php-model/grpc-proto/src/UserModel.php b/seed/php-model/grpc-proto/src/UserModel.php
index b28be2ae864..7072af6d470 100644
--- a/seed/php-model/grpc-proto/src/UserModel.php
+++ b/seed/php-model/grpc-proto/src/UserModel.php
@@ -35,7 +35,7 @@ class UserModel extends JsonSerializableType
/**
* @var array|array|null $metadata
*/
- #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $metadata;
/**
diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DeleteRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DeleteRequest.php
index 4bc66433a79..ab476a3d1b7 100644
--- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DeleteRequest.php
+++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DeleteRequest.php
@@ -30,7 +30,7 @@ class DeleteRequest extends JsonSerializableType
/**
* @var array|array|null $filter
*/
- #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $filter;
/**
diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DescribeRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DescribeRequest.php
index 1a527e70876..7c3319bf6b7 100644
--- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DescribeRequest.php
+++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/DescribeRequest.php
@@ -11,7 +11,7 @@ class DescribeRequest extends JsonSerializableType
/**
* @var array|array|null $filter
*/
- #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $filter;
/**
diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/QueryRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/QueryRequest.php
index e91a72ecdf8..bcbb33495c6 100644
--- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/QueryRequest.php
+++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/QueryRequest.php
@@ -26,7 +26,7 @@ class QueryRequest extends JsonSerializableType
/**
* @var array|array|null $filter
*/
- #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $filter;
/**
diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UpdateRequest.php b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UpdateRequest.php
index 31fc8adcacc..a6ac2c80eaa 100644
--- a/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UpdateRequest.php
+++ b/seed/php-sdk/grpc-proto-exhaustive/src/Dataservice/Requests/UpdateRequest.php
@@ -25,7 +25,7 @@ class UpdateRequest extends JsonSerializableType
/**
* @var array|array|null $setMetadata
*/
- #[JsonProperty('setMetadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('setMetadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $setMetadata;
/**
diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/Column.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/Column.php
index 956015cca43..6450e66da03 100644
--- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/Column.php
+++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/Column.php
@@ -24,7 +24,7 @@ class Column extends JsonSerializableType
/**
* @var array|array|null $metadata
*/
- #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $metadata;
/**
diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryColumn.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryColumn.php
index 65b22409292..71a7e5b693b 100644
--- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryColumn.php
+++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/QueryColumn.php
@@ -30,7 +30,7 @@ class QueryColumn extends JsonSerializableType
/**
* @var array|array|null $filter
*/
- #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('filter'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $filter;
/**
diff --git a/seed/php-sdk/grpc-proto-exhaustive/src/Types/ScoredColumn.php b/seed/php-sdk/grpc-proto-exhaustive/src/Types/ScoredColumn.php
index 1c276d7ab0b..768fd0ac7b7 100644
--- a/seed/php-sdk/grpc-proto-exhaustive/src/Types/ScoredColumn.php
+++ b/seed/php-sdk/grpc-proto-exhaustive/src/Types/ScoredColumn.php
@@ -30,7 +30,7 @@ class ScoredColumn extends JsonSerializableType
/**
* @var array|array|null $metadata
*/
- #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $metadata;
/**
diff --git a/seed/php-sdk/grpc-proto/src/Types/UserModel.php b/seed/php-sdk/grpc-proto/src/Types/UserModel.php
index e893f5fff06..a666ae3e7a7 100644
--- a/seed/php-sdk/grpc-proto/src/Types/UserModel.php
+++ b/seed/php-sdk/grpc-proto/src/Types/UserModel.php
@@ -35,7 +35,7 @@ class UserModel extends JsonSerializableType
/**
* @var array|array|null $metadata
*/
- #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $metadata;
/**
diff --git a/seed/php-sdk/grpc-proto/src/Userservice/Requests/CreateRequest.php b/seed/php-sdk/grpc-proto/src/Userservice/Requests/CreateRequest.php
index 6314f884cd1..15c1426fe36 100644
--- a/seed/php-sdk/grpc-proto/src/Userservice/Requests/CreateRequest.php
+++ b/seed/php-sdk/grpc-proto/src/Userservice/Requests/CreateRequest.php
@@ -35,7 +35,7 @@ class CreateRequest extends JsonSerializableType
/**
* @var array|array|null $metadata
*/
- #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'])]
+ #[JsonProperty('metadata'), Union(['string' => new Union('float', 'string', 'bool')], ['string' => 'mixed'], 'null')]
public array|null $metadata;
/**
diff --git a/seed/postman/server-sent-events/collection.json b/seed/postman/server-sent-events/collection.json
index a0cea86dec9..857ab05350e 100644
--- a/seed/postman/server-sent-events/collection.json
+++ b/seed/postman/server-sent-events/collection.json
@@ -34,12 +34,18 @@
"query": [],
"variable": []
},
- "header": [],
+ "header": [
+ {
+ "type": "text",
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
"method": "POST",
"auth": null,
"body": {
"mode": "raw",
- "raw": "{\n \"query\": \"example\"\n}",
+ "raw": "{\n \"query\": \"string\"\n}",
"options": {
"raw": {
"language": "json"
@@ -47,7 +53,48 @@
}
}
},
- "response": []
+ "response": [
+ {
+ "name": "Success",
+ "status": "OK",
+ "code": 200,
+ "originalRequest": {
+ "description": null,
+ "url": {
+ "raw": "{{baseUrl}}/stream",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "stream"
+ ],
+ "query": [],
+ "variable": []
+ },
+ "header": [
+ {
+ "type": "text",
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "method": "POST",
+ "auth": null,
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"query\": \"string\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ }
+ },
+ "description": null,
+ "body": "[\n {\n \"delta\": \"string\",\n \"tokens\": 1\n },\n {\n \"delta\": \"string\",\n \"tokens\": 1\n }\n]",
+ "_postman_previewlanguage": "json"
+ }
+ ]
}
]
}
diff --git a/seed/pydantic/server-sent-events/.github/workflows/ci.yml b/seed/pydantic/server-sent-events/.github/workflows/ci.yml
index 17b9d4fa0e8..b204fa604e2 100644
--- a/seed/pydantic/server-sent-events/.github/workflows/ci.yml
+++ b/seed/pydantic/server-sent-events/.github/workflows/ci.yml
@@ -34,7 +34,7 @@ jobs:
run: poetry install
- name: Test
- run: poetry run pytest ./tests/custom/
+ run: poetry run pytest -rP .
publish:
needs: [compile, test]
diff --git a/seed/pydantic/server-sent-events/pyproject.toml b/seed/pydantic/server-sent-events/pyproject.toml
index 3ecc58493cc..e5b0d831e44 100644
--- a/seed/pydantic/server-sent-events/pyproject.toml
+++ b/seed/pydantic/server-sent-events/pyproject.toml
@@ -50,6 +50,9 @@ asyncio_mode = "auto"
[tool.mypy]
plugins = ["pydantic.mypy"]
+[tool.ruff]
+line-length = 120
+
[build-system]
requires = ["poetry-core"]
diff --git a/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/__init__.py b/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/__init__.py
index 85460274fba..9c7cd65aa25 100644
--- a/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/__init__.py
+++ b/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/__init__.py
@@ -5,7 +5,6 @@
IS_PYDANTIC_V2,
UniversalBaseModel,
UniversalRootModel,
- deep_union_pydantic_dicts,
parse_obj_as,
universal_field_validator,
universal_root_validator,
@@ -18,7 +17,6 @@
"IS_PYDANTIC_V2",
"UniversalBaseModel",
"UniversalRootModel",
- "deep_union_pydantic_dicts",
"parse_obj_as",
"serialize_datetime",
"universal_field_validator",
diff --git a/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/datetime_utils.py b/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/datetime_utils.py
index 47344e9d9cc..7c9864a944c 100644
--- a/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/datetime_utils.py
+++ b/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/datetime_utils.py
@@ -13,9 +13,7 @@ def serialize_datetime(v: dt.datetime) -> str:
"""
def _serialize_zoned_datetime(v: dt.datetime) -> str:
- if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(
- None
- ):
+ if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None):
# UTC is a special case where we use "Z" at the end instead of "+00:00"
return v.isoformat().replace("+00:00", "Z")
else:
diff --git a/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/pydantic_utilities.py b/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/pydantic_utilities.py
index 1a4cd514c6a..bbe1de41431 100644
--- a/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/pydantic_utilities.py
+++ b/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/pydantic_utilities.py
@@ -4,7 +4,8 @@
import datetime as dt
import typing
from collections import defaultdict
-from functools import wraps
+
+import typing_extensions
import pydantic
@@ -54,19 +55,6 @@
Model = typing.TypeVar("Model", bound=pydantic.BaseModel)
-def deep_union_pydantic_dicts(
- source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any]
-) -> typing.Dict[str, typing.Any]:
- for key, value in source.items():
- if isinstance(value, dict):
- node = destination.setdefault(key, {})
- deep_union_pydantic_dicts(value, node)
- else:
- destination[key] = value
-
- return destination
-
-
def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T:
if IS_PYDANTIC_V2:
adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2
@@ -92,6 +80,8 @@ class Config:
smart_union = True
allow_population_by_field_name = True
json_encoders = {dt.datetime: serialize_datetime}
+ # Allow fields begining with `model_` to be used in the model
+ protected_namespaces = ()
def json(self, **kwargs: typing.Any) -> str:
kwargs_with_defaults: typing.Any = {
@@ -105,44 +95,107 @@ def json(self, **kwargs: typing.Any) -> str:
return super().json(**kwargs_with_defaults)
def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
- kwargs_with_defaults_exclude_unset: typing.Any = {
- "by_alias": True,
- "exclude_unset": True,
- **kwargs,
- }
- kwargs_with_defaults_exclude_none: typing.Any = {
- "by_alias": True,
- "exclude_none": True,
- **kwargs,
- }
-
+ """
+ Override the default dict method to `exclude_unset` by default. This function patches
+ `exclude_unset` to work include fields within non-None default values.
+ """
+ # Note: the logic here is multi-plexed given the levers exposed in Pydantic V1 vs V2
+ # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice.
+ #
+ # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models
+ # that we have less control over, and this is less intrusive than custom serializers for now.
if IS_PYDANTIC_V2:
+ kwargs_with_defaults_exclude_unset: typing.Any = {
+ **kwargs,
+ "by_alias": True,
+ "exclude_unset": True,
+ "exclude_none": False,
+ }
+ kwargs_with_defaults_exclude_none: typing.Any = {
+ **kwargs,
+ "by_alias": True,
+ "exclude_none": True,
+ "exclude_unset": False,
+ }
return deep_union_pydantic_dicts(
super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2
super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2
)
+
else:
- return deep_union_pydantic_dicts(
- super().dict(**kwargs_with_defaults_exclude_unset),
- super().dict(**kwargs_with_defaults_exclude_none),
- )
+ _fields_set = self.__fields_set__.copy()
+
+ fields = _get_model_fields(self.__class__)
+ for name, field in fields.items():
+ if name not in _fields_set:
+ default = _get_field_default(field)
+
+ # If the default values are non-null act like they've been set
+ # This effectively allows exclude_unset to work like exclude_none where
+ # the latter passes through intentionally set none values.
+ if default is not None or ("exclude_unset" in kwargs and not kwargs["exclude_unset"]):
+ _fields_set.add(name)
+
+ if default is not None:
+ self.__fields_set__.add(name)
+
+ kwargs_with_defaults_exclude_unset_include_fields: typing.Any = {
+ "by_alias": True,
+ "exclude_unset": True,
+ "include": _fields_set,
+ **kwargs,
+ }
+
+ return super().dict(**kwargs_with_defaults_exclude_unset_include_fields)
+
+
+def _union_list_of_pydantic_dicts(
+ source: typing.List[typing.Any], destination: typing.List[typing.Any]
+) -> typing.List[typing.Any]:
+ converted_list: typing.List[typing.Any] = []
+ for i, item in enumerate(source):
+ destination_value = destination[i] # type: ignore
+ if isinstance(item, dict):
+ converted_list.append(deep_union_pydantic_dicts(item, destination_value))
+ elif isinstance(item, list):
+ converted_list.append(_union_list_of_pydantic_dicts(item, destination_value))
+ else:
+ converted_list.append(item)
+ return converted_list
+
+
+def deep_union_pydantic_dicts(
+ source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any]
+) -> typing.Dict[str, typing.Any]:
+ for key, value in source.items():
+ node = destination.setdefault(key, {})
+ if isinstance(value, dict):
+ deep_union_pydantic_dicts(value, node)
+ # Note: we do not do this same processing for sets given we do not have sets of models
+ # and given the sets are unordered, the processing of the set and matching objects would
+ # be non-trivial.
+ elif isinstance(value, list):
+ destination[key] = _union_list_of_pydantic_dicts(value, node)
+ else:
+ destination[key] = value
+
+ return destination
-UniversalRootModel: typing.Type[pydantic.BaseModel]
if IS_PYDANTIC_V2:
class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore # Pydantic v2
pass
- UniversalRootModel = V2RootModel
+ UniversalRootModel: typing_extensions.TypeAlias = V2RootModel # type: ignore
else:
- UniversalRootModel = UniversalBaseModel
+ UniversalRootModel: typing_extensions.TypeAlias = UniversalBaseModel # type: ignore
def encode_by_type(o: typing.Any) -> typing.Any:
- encoders_by_class_tuples: typing.Dict[
- typing.Callable[[typing.Any], typing.Any], typing.Tuple[typing.Any, ...]
- ] = defaultdict(tuple)
+ encoders_by_class_tuples: typing.Dict[typing.Callable[[typing.Any], typing.Any], typing.Tuple[typing.Any, ...]] = (
+ defaultdict(tuple)
+ )
for type_, encoder in encoders_by_type.items():
encoders_by_class_tuples[encoder] += (type_,)
@@ -153,11 +206,11 @@ def encode_by_type(o: typing.Any) -> typing.Any:
return encoder(o)
-def update_forward_refs(model: typing.Type["Model"]) -> None:
+def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None:
if IS_PYDANTIC_V2:
model.model_rebuild(raise_errors=False) # type: ignore # Pydantic v2
else:
- model.update_forward_refs()
+ model.update_forward_refs(**localns)
# Mirrors Pydantic's internal typing
@@ -168,37 +221,45 @@ def universal_root_validator(
pre: bool = False,
) -> typing.Callable[[AnyCallable], AnyCallable]:
def decorator(func: AnyCallable) -> AnyCallable:
- @wraps(func)
- def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable:
- if IS_PYDANTIC_V2:
- wrapped_func = pydantic.model_validator("before" if pre else "after")(
- func
- ) # type: ignore # Pydantic v2
- else:
- wrapped_func = pydantic.root_validator(pre=pre)(func) # type: ignore # Pydantic v1
+ if IS_PYDANTIC_V2:
+ return pydantic.model_validator(mode="before" if pre else "after")(func) # type: ignore # Pydantic v2
+ else:
+ return pydantic.root_validator(pre=pre)(func) # type: ignore # Pydantic v1
- return wrapped_func(*args, **kwargs)
+ return decorator
- return validate
+
+def universal_field_validator(field_name: str, pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]:
+ def decorator(func: AnyCallable) -> AnyCallable:
+ if IS_PYDANTIC_V2:
+ return pydantic.field_validator(field_name, mode="before" if pre else "after")(func) # type: ignore # Pydantic v2
+ else:
+ return pydantic.validator(field_name, pre=pre)(func) # type: ignore # Pydantic v1
return decorator
-def universal_field_validator(
- field_name: str, pre: bool = False
-) -> typing.Callable[[AnyCallable], AnyCallable]:
- def decorator(func: AnyCallable) -> AnyCallable:
- @wraps(func)
- def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable:
- if IS_PYDANTIC_V2:
- wrapped_func = pydantic.field_validator(
- field_name, mode="before" if pre else "after"
- )(func) # type: ignore # Pydantic v2
- else:
- wrapped_func = pydantic.validator(field_name, pre=pre)(func)
+PydanticField = typing.Union[ModelField, pydantic.fields.FieldInfo]
- return wrapped_func(*args, **kwargs)
- return validate
+def _get_model_fields(
+ model: typing.Type["Model"],
+) -> typing.Mapping[str, PydanticField]:
+ if IS_PYDANTIC_V2:
+ return model.model_fields # type: ignore # Pydantic v2
+ else:
+ return model.__fields__ # type: ignore # Pydantic v1
- return decorator
+
+def _get_field_default(field: PydanticField) -> typing.Any:
+ try:
+ value = field.get_default() # type: ignore # Pydantic < v1.10.15
+ except:
+ value = field.default
+ if IS_PYDANTIC_V2:
+ from pydantic_core import PydanticUndefined
+
+ if value == PydanticUndefined:
+ return None
+ return value
+ return value
diff --git a/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/serialization.py b/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/serialization.py
index 5400ca0bc3b..cb5dcbf93a9 100644
--- a/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/serialization.py
+++ b/seed/pydantic/server-sent-events/src/seed/server_sent_events/core/serialization.py
@@ -1,10 +1,13 @@
# This file was auto-generated by Fern from our API Definition.
import collections
+import inspect
import typing
import typing_extensions
+import pydantic
+
class FieldMetadata:
"""
@@ -29,6 +32,7 @@ def convert_and_respect_annotation_metadata(
object_: typing.Any,
annotation: typing.Any,
inner_type: typing.Optional[typing.Any] = None,
+ direction: typing.Literal["read", "write"],
) -> typing.Any:
"""
Respect the metadata annotations on a field, such as aliasing. This function effectively
@@ -56,49 +60,77 @@ def convert_and_respect_annotation_metadata(
inner_type = annotation
clean_type = _remove_annotations(inner_type)
- if typing_extensions.is_typeddict(clean_type) and isinstance(
- object_, typing.Mapping
+ # Pydantic models
+ if (
+ inspect.isclass(clean_type)
+ and issubclass(clean_type, pydantic.BaseModel)
+ and isinstance(object_, typing.Mapping)
):
- return _convert_typeddict(object_, clean_type)
+ return _convert_mapping(object_, clean_type, direction)
+ # TypedDicts
+ if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping):
+ return _convert_mapping(object_, clean_type, direction)
if (
- # If you're iterating on a string, do not bother to coerce it to a sequence.
- (not isinstance(object_, str))
- and (
- (
- (
- typing_extensions.get_origin(clean_type) == typing.List
- or typing_extensions.get_origin(clean_type) == list
- or clean_type == typing.List
- )
- and isinstance(object_, typing.List)
- )
- or (
- (
- typing_extensions.get_origin(clean_type) == typing.Set
- or typing_extensions.get_origin(clean_type) == set
- or clean_type == typing.Set
- )
- and isinstance(object_, typing.Set)
+ typing_extensions.get_origin(clean_type) == typing.Dict
+ or typing_extensions.get_origin(clean_type) == dict
+ or clean_type == typing.Dict
+ ) and isinstance(object_, typing.Dict):
+ key_type = typing_extensions.get_args(clean_type)[0]
+ value_type = typing_extensions.get_args(clean_type)[1]
+
+ return {
+ key: convert_and_respect_annotation_metadata(
+ object_=value,
+ annotation=annotation,
+ inner_type=value_type,
+ direction=direction,
)
- or (
- (
- typing_extensions.get_origin(clean_type) == typing.Sequence
- or typing_extensions.get_origin(clean_type)
- == collections.abc.Sequence
- or clean_type == typing.Sequence
+ for key, value in object_.items()
+ }
+
+ # If you're iterating on a string, do not bother to coerce it to a sequence.
+ if not isinstance(object_, str):
+ if (
+ typing_extensions.get_origin(clean_type) == typing.Set
+ or typing_extensions.get_origin(clean_type) == set
+ or clean_type == typing.Set
+ ) and isinstance(object_, typing.Set):
+ inner_type = typing_extensions.get_args(clean_type)[0]
+ return {
+ convert_and_respect_annotation_metadata(
+ object_=item,
+ annotation=annotation,
+ inner_type=inner_type,
+ direction=direction,
)
- and isinstance(object_, typing.Sequence)
+ for item in object_
+ }
+ elif (
+ (
+ typing_extensions.get_origin(clean_type) == typing.List
+ or typing_extensions.get_origin(clean_type) == list
+ or clean_type == typing.List
)
- )
- ):
- inner_type = typing_extensions.get_args(clean_type)[0]
- return [
- convert_and_respect_annotation_metadata(
- object_=item, annotation=annotation, inner_type=inner_type
+ and isinstance(object_, typing.List)
+ ) or (
+ (
+ typing_extensions.get_origin(clean_type) == typing.Sequence
+ or typing_extensions.get_origin(clean_type) == collections.abc.Sequence
+ or clean_type == typing.Sequence
)
- for item in object_
- ]
+ and isinstance(object_, typing.Sequence)
+ ):
+ inner_type = typing_extensions.get_args(clean_type)[0]
+ return [
+ convert_and_respect_annotation_metadata(
+ object_=item,
+ annotation=annotation,
+ inner_type=inner_type,
+ direction=direction,
+ )
+ for item in object_
+ ]
if typing_extensions.get_origin(clean_type) == typing.Union:
# We should be able to ~relatively~ safely try to convert keys against all
@@ -107,7 +139,10 @@ def convert_and_respect_annotation_metadata(
# Or if another member aliases a field of the same name that another member does not.
for member in typing_extensions.get_args(clean_type):
object_ = convert_and_respect_annotation_metadata(
- object_=object_, annotation=annotation, inner_type=member
+ object_=object_,
+ annotation=annotation,
+ inner_type=member,
+ direction=direction,
)
return object_
@@ -120,18 +155,34 @@ def convert_and_respect_annotation_metadata(
return object_
-def _convert_typeddict(
- object_: typing.Mapping[str, object], expected_type: typing.Any
+def _convert_mapping(
+ object_: typing.Mapping[str, object],
+ expected_type: typing.Any,
+ direction: typing.Literal["read", "write"],
) -> typing.Mapping[str, object]:
converted_object: typing.Dict[str, object] = {}
annotations = typing_extensions.get_type_hints(expected_type, include_extras=True)
+ aliases_to_field_names = _get_alias_to_field_name(annotations)
for key, value in object_.items():
- type_ = annotations.get(key)
+ if direction == "read" and key in aliases_to_field_names:
+ dealiased_key = aliases_to_field_names.get(key)
+ if dealiased_key is not None:
+ type_ = annotations.get(dealiased_key)
+ else:
+ type_ = annotations.get(key)
+ # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map
+ #
+ # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias
+ # then we can just pass the value through as is
if type_ is None:
converted_object[key] = value
+ elif direction == "read" and key not in aliases_to_field_names:
+ converted_object[key] = convert_and_respect_annotation_metadata(
+ object_=value, annotation=type_, direction=direction
+ )
else:
- converted_object[_alias_key(key, type_)] = (
- convert_and_respect_annotation_metadata(object_=value, annotation=type_)
+ converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = (
+ convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction)
)
return converted_object
@@ -165,7 +216,39 @@ def _remove_annotations(type_: typing.Any) -> typing.Any:
return type_
-def _alias_key(key: str, type_: typing.Any) -> str:
+def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]:
+ annotations = typing_extensions.get_type_hints(type_, include_extras=True)
+ return _get_alias_to_field_name(annotations)
+
+
+def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]:
+ annotations = typing_extensions.get_type_hints(type_, include_extras=True)
+ return _get_field_to_alias_name(annotations)
+
+
+def _get_alias_to_field_name(
+ field_to_hint: typing.Dict[str, typing.Any],
+) -> typing.Dict[str, str]:
+ aliases = {}
+ for field, hint in field_to_hint.items():
+ maybe_alias = _get_alias_from_type(hint)
+ if maybe_alias is not None:
+ aliases[maybe_alias] = field
+ return aliases
+
+
+def _get_field_to_alias_name(
+ field_to_hint: typing.Dict[str, typing.Any],
+) -> typing.Dict[str, str]:
+ aliases = {}
+ for field, hint in field_to_hint.items():
+ maybe_alias = _get_alias_from_type(hint)
+ if maybe_alias is not None:
+ aliases[field] = maybe_alias
+ return aliases
+
+
+def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]:
maybe_annotated_type = _get_annotation(type_)
if maybe_annotated_type is not None:
@@ -175,5 +258,15 @@ def _alias_key(key: str, type_: typing.Any) -> str:
for annotation in annotations:
if isinstance(annotation, FieldMetadata) and annotation.alias is not None:
return annotation.alias
+ return None
+
- return key
+def _alias_key(
+ key: str,
+ type_: typing.Any,
+ direction: typing.Literal["read", "write"],
+ aliases_to_field_names: typing.Dict[str, str],
+) -> str:
+ if direction == "read":
+ return aliases_to_field_names.get(key, key)
+ return _get_alias_from_type(type_=type_) or key
diff --git a/seed/pydantic/server-sent-events/src/seed/server_sent_events/resources/completions/streamed_completion.py b/seed/pydantic/server-sent-events/src/seed/server_sent_events/resources/completions/streamed_completion.py
index 2a6089db912..1d8a2c11dd3 100644
--- a/seed/pydantic/server-sent-events/src/seed/server_sent_events/resources/completions/streamed_completion.py
+++ b/seed/pydantic/server-sent-events/src/seed/server_sent_events/resources/completions/streamed_completion.py
@@ -11,9 +11,7 @@ class StreamedCompletion(UniversalBaseModel):
tokens: typing.Optional[int] = None
if IS_PYDANTIC_V2:
- model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(
- extra="allow"
- ) # type: ignore # Pydantic v2
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2
else:
class Config:
diff --git a/seed/ruby-model/undiscriminated-unions/lib/requests.rb b/seed/ruby-model/undiscriminated-unions/lib/requests.rb
deleted file mode 100644
index 3ebe1c4d180..00000000000
--- a/seed/ruby-model/undiscriminated-unions/lib/requests.rb
+++ /dev/null
@@ -1,132 +0,0 @@
-# frozen_string_literal: true
-
-require "faraday"
-require "faraday/retry"
-require "async/http/faraday"
-
-module SeedUndiscriminatedUnionsClient
- class RequestClient
- # @return [Faraday]
- attr_reader :conn
- # @return [String]
- attr_reader :base_url
-
- # @param base_url [String]
- # @param max_retries [Long] The number of times to retry a failed request, defaults to 2.
- # @param timeout_in_seconds [Long]
- # @return [SeedUndiscriminatedUnionsClient::RequestClient]
- def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil)
- @base_url = base_url
- @conn = Faraday.new do |faraday|
- faraday.request :json
- faraday.response :raise_error, include_request: true
- faraday.request :retry, { max: max_retries } unless max_retries.nil?
- faraday.options.timeout = timeout_in_seconds unless timeout_in_seconds.nil?
- end
- end
-
- # @param request_options [SeedUndiscriminatedUnionsClient::RequestOptions]
- # @return [String]
- def get_url(request_options: nil)
- request_options&.base_url || @base_url
- end
-
- # @return [Hash{String => String}]
- def get_headers
- { "X-Fern-Language": "Ruby", "X-Fern-SDK-Name": "seed_undiscriminated_unions_client" }
- end
- end
-
- class AsyncRequestClient
- # @return [Faraday]
- attr_reader :conn
- # @return [String]
- attr_reader :base_url
-
- # @param base_url [String]
- # @param max_retries [Long] The number of times to retry a failed request, defaults to 2.
- # @param timeout_in_seconds [Long]
- # @return [SeedUndiscriminatedUnionsClient::AsyncRequestClient]
- def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil)
- @base_url = base_url
- @conn = Faraday.new do |faraday|
- faraday.request :json
- faraday.response :raise_error, include_request: true
- faraday.adapter :async_http
- faraday.request :retry, { max: max_retries } unless max_retries.nil?
- faraday.options.timeout = timeout_in_seconds unless timeout_in_seconds.nil?
- end
- end
-
- # @param request_options [SeedUndiscriminatedUnionsClient::RequestOptions]
- # @return [String]
- def get_url(request_options: nil)
- request_options&.base_url || @base_url
- end
-
- # @return [Hash{String => String}]
- def get_headers
- { "X-Fern-Language": "Ruby", "X-Fern-SDK-Name": "seed_undiscriminated_unions_client" }
- end
- end
-
- # Additional options for request-specific configuration when calling APIs via the
- # SDK.
- class RequestOptions
- # @return [String]
- attr_reader :base_url
- # @return [Hash{String => Object}]
- attr_reader :additional_headers
- # @return [Hash{String => Object}]
- attr_reader :additional_query_parameters
- # @return [Hash{String => Object}]
- attr_reader :additional_body_parameters
- # @return [Long]
- attr_reader :timeout_in_seconds
-
- # @param base_url [String]
- # @param additional_headers [Hash{String => Object}]
- # @param additional_query_parameters [Hash{String => Object}]
- # @param additional_body_parameters [Hash{String => Object}]
- # @param timeout_in_seconds [Long]
- # @return [SeedUndiscriminatedUnionsClient::RequestOptions]
- def initialize(base_url: nil, additional_headers: nil, additional_query_parameters: nil,
- additional_body_parameters: nil, timeout_in_seconds: nil)
- @base_url = base_url
- @additional_headers = additional_headers
- @additional_query_parameters = additional_query_parameters
- @additional_body_parameters = additional_body_parameters
- @timeout_in_seconds = timeout_in_seconds
- end
- end
-
- # Additional options for request-specific configuration when calling APIs via the
- # SDK.
- class IdempotencyRequestOptions
- # @return [String]
- attr_reader :base_url
- # @return [Hash{String => Object}]
- attr_reader :additional_headers
- # @return [Hash{String => Object}]
- attr_reader :additional_query_parameters
- # @return [Hash{String => Object}]
- attr_reader :additional_body_parameters
- # @return [Long]
- attr_reader :timeout_in_seconds
-
- # @param base_url [String]
- # @param additional_headers [Hash{String => Object}]
- # @param additional_query_parameters [Hash{String => Object}]
- # @param additional_body_parameters [Hash{String => Object}]
- # @param timeout_in_seconds [Long]
- # @return [SeedUndiscriminatedUnionsClient::IdempotencyRequestOptions]
- def initialize(base_url: nil, additional_headers: nil, additional_query_parameters: nil,
- additional_body_parameters: nil, timeout_in_seconds: nil)
- @base_url = base_url
- @additional_headers = additional_headers
- @additional_query_parameters = additional_query_parameters
- @additional_body_parameters = additional_body_parameters
- @timeout_in_seconds = timeout_in_seconds
- end
- end
-end
diff --git a/seed/ruby-model/undiscriminated-unions/lib/seed_undiscriminated_unions_client.rb b/seed/ruby-model/undiscriminated-unions/lib/seed_undiscriminated_unions_client.rb
index f409a52e7d3..e6fbf023528 100644
--- a/seed/ruby-model/undiscriminated-unions/lib/seed_undiscriminated_unions_client.rb
+++ b/seed/ruby-model/undiscriminated-unions/lib/seed_undiscriminated_unions_client.rb
@@ -1,43 +1,7 @@
# frozen_string_literal: true
-require_relative "types_export"
-require_relative "requests"
-require_relative "seed_undiscriminated_unions_client/union/client"
-
-module SeedUndiscriminatedUnionsClient
- class Client
- # @return [SeedUndiscriminatedUnionsClient::UnionClient]
- attr_reader :union
-
- # @param base_url [String]
- # @param max_retries [Long] The number of times to retry a failed request, defaults to 2.
- # @param timeout_in_seconds [Long]
- # @return [SeedUndiscriminatedUnionsClient::Client]
- def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil)
- @request_client = SeedUndiscriminatedUnionsClient::RequestClient.new(
- base_url: base_url,
- max_retries: max_retries,
- timeout_in_seconds: timeout_in_seconds
- )
- @union = SeedUndiscriminatedUnionsClient::UnionClient.new(request_client: @request_client)
- end
- end
-
- class AsyncClient
- # @return [SeedUndiscriminatedUnionsClient::AsyncUnionClient]
- attr_reader :union
-
- # @param base_url [String]
- # @param max_retries [Long] The number of times to retry a failed request, defaults to 2.
- # @param timeout_in_seconds [Long]
- # @return [SeedUndiscriminatedUnionsClient::AsyncClient]
- def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil)
- @async_request_client = SeedUndiscriminatedUnionsClient::AsyncRequestClient.new(
- base_url: base_url,
- max_retries: max_retries,
- timeout_in_seconds: timeout_in_seconds
- )
- @union = SeedUndiscriminatedUnionsClient::AsyncUnionClient.new(request_client: @async_request_client)
- end
- end
-end
+require_relative "seed_undiscriminated_unions_client/union/types/type_with_optional_union"
+require_relative "seed_undiscriminated_unions_client/union/types/my_union"
+require_relative "seed_undiscriminated_unions_client/union/types/key_type"
+require_relative "seed_undiscriminated_unions_client/union/types/key"
+require_relative "seed_undiscriminated_unions_client/union/types/metadata"
diff --git a/seed/ruby-model/undiscriminated-unions/lib/seed_undiscriminated_unions_client/union/client.rb b/seed/ruby-model/undiscriminated-unions/lib/seed_undiscriminated_unions_client/union/client.rb
deleted file mode 100644
index 615ab6c5d1e..00000000000
--- a/seed/ruby-model/undiscriminated-unions/lib/seed_undiscriminated_unions_client/union/client.rb
+++ /dev/null
@@ -1,130 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "../../requests"
-require_relative "types/my_union"
-require_relative "types/metadata"
-require "json"
-require "async"
-
-module SeedUndiscriminatedUnionsClient
- class UnionClient
- # @return [SeedUndiscriminatedUnionsClient::RequestClient]
- attr_reader :request_client
-
- # @param request_client [SeedUndiscriminatedUnionsClient::RequestClient]
- # @return [SeedUndiscriminatedUnionsClient::UnionClient]
- def initialize(request_client:)
- @request_client = request_client
- end
-
- # @param request [String, Array, Integer, Array, Array>, Set]
- # @param request_options [SeedUndiscriminatedUnionsClient::RequestOptions]
- # @return [String, Array, Integer, Array, Array>, Set]
- # @example
- # undiscriminated_unions = SeedUndiscriminatedUnionsClient::Client.new(base_url: "https://api.example.com")
- # undiscriminated_unions.union.get(request: "string")
- def get(request:, request_options: nil)
- response = @request_client.conn.post do |req|
- req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil?
- req.headers = {
- **(req.headers || {}),
- **@request_client.get_headers,
- **(request_options&.additional_headers || {})
- }.compact
- unless request_options.nil? || request_options&.additional_query_parameters.nil?
- req.params = { **(request_options&.additional_query_parameters || {}) }.compact
- end
- req.body = { **(request || {}), **(request_options&.additional_body_parameters || {}) }.compact
- req.url "#{@request_client.get_url(request_options: request_options)}/"
- end
- SeedUndiscriminatedUnionsClient::Union::MyUnion.from_json(json_object: response.body)
- end
-
- # @param request_options [SeedUndiscriminatedUnionsClient::RequestOptions]
- # @return [SeedUndiscriminatedUnionsClient::Union::METADATA]
- # @example
- # undiscriminated_unions = SeedUndiscriminatedUnionsClient::Client.new(base_url: "https://api.example.com")
- # undiscriminated_unions.union.get_metadata
- def get_metadata(request_options: nil)
- response = @request_client.conn.get do |req|
- req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil?
- req.headers = {
- **(req.headers || {}),
- **@request_client.get_headers,
- **(request_options&.additional_headers || {})
- }.compact
- unless request_options.nil? || request_options&.additional_query_parameters.nil?
- req.params = { **(request_options&.additional_query_parameters || {}) }.compact
- end
- unless request_options.nil? || request_options&.additional_body_parameters.nil?
- req.body = { **(request_options&.additional_body_parameters || {}) }.compact
- end
- req.url "#{@request_client.get_url(request_options: request_options)}/metadata"
- end
- JSON.parse(response.body)
- end
- end
-
- class AsyncUnionClient
- # @return [SeedUndiscriminatedUnionsClient::AsyncRequestClient]
- attr_reader :request_client
-
- # @param request_client [SeedUndiscriminatedUnionsClient::AsyncRequestClient]
- # @return [SeedUndiscriminatedUnionsClient::AsyncUnionClient]
- def initialize(request_client:)
- @request_client = request_client
- end
-
- # @param request [String, Array, Integer, Array, Array>, Set]
- # @param request_options [SeedUndiscriminatedUnionsClient::RequestOptions]
- # @return [String, Array, Integer, Array, Array>, Set]
- # @example
- # undiscriminated_unions = SeedUndiscriminatedUnionsClient::Client.new(base_url: "https://api.example.com")
- # undiscriminated_unions.union.get(request: "string")
- def get(request:, request_options: nil)
- Async do
- response = @request_client.conn.post do |req|
- req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil?
- req.headers = {
- **(req.headers || {}),
- **@request_client.get_headers,
- **(request_options&.additional_headers || {})
- }.compact
- unless request_options.nil? || request_options&.additional_query_parameters.nil?
- req.params = { **(request_options&.additional_query_parameters || {}) }.compact
- end
- req.body = { **(request || {}), **(request_options&.additional_body_parameters || {}) }.compact
- req.url "#{@request_client.get_url(request_options: request_options)}/"
- end
- SeedUndiscriminatedUnionsClient::Union::MyUnion.from_json(json_object: response.body)
- end
- end
-
- # @param request_options [SeedUndiscriminatedUnionsClient::RequestOptions]
- # @return [SeedUndiscriminatedUnionsClient::Union::METADATA]
- # @example
- # undiscriminated_unions = SeedUndiscriminatedUnionsClient::Client.new(base_url: "https://api.example.com")
- # undiscriminated_unions.union.get_metadata
- def get_metadata(request_options: nil)
- Async do
- response = @request_client.conn.get do |req|
- req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil?
- req.headers = {
- **(req.headers || {}),
- **@request_client.get_headers,
- **(request_options&.additional_headers || {})
- }.compact
- unless request_options.nil? || request_options&.additional_query_parameters.nil?
- req.params = { **(request_options&.additional_query_parameters || {}) }.compact
- end
- unless request_options.nil? || request_options&.additional_body_parameters.nil?
- req.body = { **(request_options&.additional_body_parameters || {}) }.compact
- end
- req.url "#{@request_client.get_url(request_options: request_options)}/metadata"
- end
- parsed_json = JSON.parse(response.body)
- parsed_json
- end
- end
- end
-end
diff --git a/seed/ruby-model/undiscriminated-unions/lib/types_export.rb b/seed/ruby-model/undiscriminated-unions/lib/types_export.rb
deleted file mode 100644
index e6fbf023528..00000000000
--- a/seed/ruby-model/undiscriminated-unions/lib/types_export.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "seed_undiscriminated_unions_client/union/types/type_with_optional_union"
-require_relative "seed_undiscriminated_unions_client/union/types/my_union"
-require_relative "seed_undiscriminated_unions_client/union/types/key_type"
-require_relative "seed_undiscriminated_unions_client/union/types/key"
-require_relative "seed_undiscriminated_unions_client/union/types/metadata"
diff --git a/seed/ruby-model/undiscriminated-unions/seed_undiscriminated_unions_client.gemspec b/seed/ruby-model/undiscriminated-unions/seed_undiscriminated_unions_client.gemspec
index 27bfc481523..62adc349d93 100644
--- a/seed/ruby-model/undiscriminated-unions/seed_undiscriminated_unions_client.gemspec
+++ b/seed/ruby-model/undiscriminated-unions/seed_undiscriminated_unions_client.gemspec
@@ -18,8 +18,4 @@ Gem::Specification.new do |spec|
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
- spec.add_dependency "async-http-faraday", ">= 0.0", "< 1.0"
- spec.add_dependency "faraday", ">= 1.10", "< 3.0"
- spec.add_dependency "faraday-net_http", ">= 1.0", "< 4.0"
- spec.add_dependency "faraday-retry", ">= 1.0", "< 3.0"
end
diff --git a/seed/ts-express/seed.yml b/seed/ts-express/seed.yml
index 66f691f2f30..37e30c303bd 100644
--- a/seed/ts-express/seed.yml
+++ b/seed/ts-express/seed.yml
@@ -85,5 +85,6 @@ allowedFailures:
- streaming
- streaming-parameter
- server-sent-events
+ - server-sent-event-examples
diff --git a/seed/ts-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/ts-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto
deleted file mode 100644
index 128799c558d..00000000000
--- a/seed/ts-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2023 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package google.api;
-
-import "google/protobuf/descriptor.proto";
-
-option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
-option java_multiple_files = true;
-option java_outer_classname = "FieldBehaviorProto";
-option java_package = "com.google.api";
-option objc_class_prefix = "GAPI";
-
-extend google.protobuf.FieldOptions {
- // A designation of a specific field behavior (required, output only, etc.)
- // in protobuf messages.
- //
- // Examples:
- //
- // string name = 1 [(google.api.field_behavior) = REQUIRED];
- // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY];
- // google.protobuf.Duration ttl = 1
- // [(google.api.field_behavior) = INPUT_ONLY];
- // google.protobuf.Timestamp expire_time = 1
- // [(google.api.field_behavior) = OUTPUT_ONLY,
- // (google.api.field_behavior) = IMMUTABLE];
- repeated google.api.FieldBehavior field_behavior = 1052;
-}
-
-// An indicator of the behavior of a given field (for example, that a field
-// is required in requests, or given as output but ignored as input).
-// This **does not** change the behavior in protocol buffers itself; it only
-// denotes the behavior and may affect how API tooling handles the field.
-//
-// Note: This enum **may** receive new values in the future.
-enum FieldBehavior {
- // Conventional default for enums. Do not use this.
- FIELD_BEHAVIOR_UNSPECIFIED = 0;
-
- // Specifically denotes a field as optional.
- // While all fields in protocol buffers are optional, this may be specified
- // for emphasis if appropriate.
- OPTIONAL = 1;
-
- // Denotes a field as required.
- // This indicates that the field **must** be provided as part of the request,
- // and failure to do so will cause an error (usually `INVALID_ARGUMENT`).
- REQUIRED = 2;
-
- // Denotes a field as output only.
- // This indicates that the field is provided in responses, but including the
- // field in a request does nothing (the server *must* ignore it and
- // *must not* throw an error as a result of the field's presence).
- OUTPUT_ONLY = 3;
-
- // Denotes a field as input only.
- // This indicates that the field is provided in requests, and the
- // corresponding field is not included in output.
- INPUT_ONLY = 4;
-
- // Denotes a field as immutable.
- // This indicates that the field may be set once in a request to create a
- // resource, but may not be changed thereafter.
- IMMUTABLE = 5;
-
- // Denotes that a (repeated) field is an unordered list.
- // This indicates that the service may provide the elements of the list
- // in any arbitrary order, rather than the order the user originally
- // provided. Additionally, the list's order may or may not be stable.
- UNORDERED_LIST = 6;
-
- // Denotes that this field returns a non-empty default value if not set.
- // This indicates that if the user provides the empty value in a request,
- // a non-empty value will be returned. The user will not be aware of what
- // non-empty value to expect.
- NON_EMPTY_DEFAULT = 7;
-
- // Denotes that the field in a resource (a message annotated with
- // google.api.resource) is used in the resource name to uniquely identify the
- // resource. For AIP-compliant APIs, this should only be applied to the
- // `name` field on the resource.
- //
- // This behavior should not be applied to references to other resources within
- // the message.
- //
- // The identifier field of resources often have different field behavior
- // depending on the request it is embedded in (e.g. for Create methods name
- // is optional and unused, while for Update methods it is required). Instead
- // of method-specific annotations, only `IDENTIFIER` is required.
- IDENTIFIER = 8;
-}
\ No newline at end of file
diff --git a/seed/ts-sdk/grpc-proto/README.md b/seed/ts-sdk/grpc-proto/README.md
index 742840c2b15..cd80361ab85 100644
--- a/seed/ts-sdk/grpc-proto/README.md
+++ b/seed/ts-sdk/grpc-proto/README.md
@@ -1,6 +1,6 @@
# Seed TypeScript Library
-[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-SDK%20generated%20by%20Fern-brightgreen)](https://github.com/fern-api/fern)
+[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript)
[![npm shield](https://img.shields.io/npm/v/@fern/grpc-proto)](https://www.npmjs.com/package/@fern/grpc-proto)
The Seed TypeScript library provides convenient access to the Seed API from TypeScript.
@@ -11,6 +11,10 @@ The Seed TypeScript library provides convenient access to the Seed API from Type
npm i -s @fern/grpc-proto
```
+## Reference
+
+A full reference for this library is available [here](./reference.md).
+
## Usage
Instantiate and use the client with the following:
diff --git a/seed/ts-sdk/server-sent-events/README.md b/seed/ts-sdk/server-sent-events/README.md
index e1128c89a07..acb70e95f5b 100644
--- a/seed/ts-sdk/server-sent-events/README.md
+++ b/seed/ts-sdk/server-sent-events/README.md
@@ -1,6 +1,6 @@
# Seed TypeScript Library
-[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-SDK%20generated%20by%20Fern-brightgreen)](https://github.com/fern-api/fern)
+[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-Built%20with%20Fern-brightgreen)](https://buildwithfern.com?utm_source=github&utm_medium=github&utm_campaign=readme&utm_source=Seed%2FTypeScript)
[![npm shield](https://img.shields.io/npm/v/@fern/server-sent-events)](https://www.npmjs.com/package/@fern/server-sent-events)
The Seed TypeScript library provides convenient access to the Seed API from TypeScript.
@@ -11,6 +11,10 @@ The Seed TypeScript library provides convenient access to the Seed API from Type
npm i -s @fern/server-sent-events
```
+## Reference
+
+A full reference for this library is available [here](./reference.md).
+
## Usage
Instantiate and use the client with the following:
diff --git a/seed/ts-sdk/server-sent-events/jest.config.js b/seed/ts-sdk/server-sent-events/jest.config.js
new file mode 100644
index 00000000000..35d6e65bf93
--- /dev/null
+++ b/seed/ts-sdk/server-sent-events/jest.config.js
@@ -0,0 +1,5 @@
+/** @type {import('jest').Config} */
+module.exports = {
+ preset: "ts-jest",
+ testEnvironment: "node",
+};
diff --git a/seed/ts-sdk/server-sent-events/package.json b/seed/ts-sdk/server-sent-events/package.json
index 3dc98b60480..1a0ddcd6a8f 100644
--- a/seed/ts-sdk/server-sent-events/package.json
+++ b/seed/ts-sdk/server-sent-events/package.json
@@ -16,13 +16,17 @@
"form-data": "^4.0.0",
"formdata-node": "^6.0.3",
"node-fetch": "2.7.0",
- "qs": "6.11.2"
+ "qs": "6.11.2",
+ "readable-stream": "^4.5.2"
},
"devDependencies": {
"@types/url-join": "4.0.1",
"@types/qs": "6.9.8",
"@types/node-fetch": "2.6.9",
+ "@types/readable-stream": "^4.0.15",
"fetch-mock-jest": "^1.5.1",
+ "webpack": "^5.94.0",
+ "ts-loader": "^9.3.1",
"jest": "29.7.0",
"@types/jest": "29.5.5",
"ts-jest": "29.1.1",
diff --git a/seed/ts-sdk/server-sent-events/resolved-snippet-templates.md b/seed/ts-sdk/server-sent-events/resolved-snippet-templates.md
new file mode 100644
index 00000000000..4422ea50f16
--- /dev/null
+++ b/seed/ts-sdk/server-sent-events/resolved-snippet-templates.md
@@ -0,0 +1,11 @@
+```typescript
+import { SeedServerSentEventsClient } from "@fern/server-sent-events";
+
+const client = new SeedServerSentEventsClient({ environment: "YOUR_BASE_URL" });
+await client.completions.stream({
+ query: "string",
+});
+
+```
+
+
diff --git a/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts b/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts
index b8504841c77..4d7b7d52e8f 100644
--- a/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts
+++ b/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts
@@ -1,4 +1,4 @@
-import type { Writable } from "stream";
+import type { Writable } from "readable-stream";
import { EventCallback, StreamWrapper } from "./chooseStreamWrapper";
export class Node18UniversalStreamWrapper
diff --git a/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts b/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts
index f9bead21841..ba5f7276750 100644
--- a/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts
+++ b/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts
@@ -1,4 +1,4 @@
-import type { Readable, Writable } from "stream";
+import type { Readable, Writable } from "readable-stream";
import { EventCallback, StreamWrapper } from "./chooseStreamWrapper";
export class NodePre18StreamWrapper implements StreamWrapper {
diff --git a/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts b/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts
index 6725061ec27..263af00911f 100644
--- a/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts
+++ b/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts
@@ -78,7 +78,7 @@ export class UndiciStreamWrapper | WritableStream): void {
+ public unpipe(dest: UndiciStreamWrapper | WritableStream): void {
this.off("data", (chunk) => {
if (dest instanceof UndiciStreamWrapper) {
dest._write(chunk);
diff --git a/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts b/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts
index d60991da089..2abd6b2ba1c 100644
--- a/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts
+++ b/seed/ts-sdk/server-sent-events/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts
@@ -1,4 +1,4 @@
-import type { Readable } from "stream";
+import type { Readable } from "readable-stream";
import { RUNTIME } from "../../runtime";
export type EventCallback = (data?: any) => void;
@@ -25,7 +25,7 @@ export async function chooseStreamWrapper(responseBody: any): Promise {
}
export const SchemaType = {
+ BIGINT: "bigint",
DATE: "date",
ENUM: "enum",
LIST: "list",
diff --git a/seed/ts-sdk/server-sent-events/src/core/schemas/builders/bigint/bigint.ts b/seed/ts-sdk/server-sent-events/src/core/schemas/builders/bigint/bigint.ts
new file mode 100644
index 00000000000..dc9c742e007
--- /dev/null
+++ b/seed/ts-sdk/server-sent-events/src/core/schemas/builders/bigint/bigint.ts
@@ -0,0 +1,50 @@
+import { BaseSchema, Schema, SchemaType } from "../../Schema";
+import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType";
+import { maybeSkipValidation } from "../../utils/maybeSkipValidation";
+import { getSchemaUtils } from "../schema-utils";
+
+export function bigint(): Schema {
+ const baseSchema: BaseSchema = {
+ parse: (raw, { breadcrumbsPrefix = [] } = {}) => {
+ if (typeof raw !== "string") {
+ return {
+ ok: false,
+ errors: [
+ {
+ path: breadcrumbsPrefix,
+ message: getErrorMessageForIncorrectType(raw, "string"),
+ },
+ ],
+ };
+ }
+ return {
+ ok: true,
+ value: BigInt(raw),
+ };
+ },
+ json: (bigint, { breadcrumbsPrefix = [] } = {}) => {
+ if (typeof bigint === "bigint") {
+ return {
+ ok: true,
+ value: bigint.toString(),
+ };
+ } else {
+ return {
+ ok: false,
+ errors: [
+ {
+ path: breadcrumbsPrefix,
+ message: getErrorMessageForIncorrectType(bigint, "bigint"),
+ },
+ ],
+ };
+ }
+ },
+ getType: () => SchemaType.BIGINT,
+ };
+
+ return {
+ ...maybeSkipValidation(baseSchema),
+ ...getSchemaUtils(baseSchema),
+ };
+}
diff --git a/seed/ts-sdk/server-sent-events/src/core/schemas/builders/bigint/index.ts b/seed/ts-sdk/server-sent-events/src/core/schemas/builders/bigint/index.ts
new file mode 100644
index 00000000000..e5843043fcb
--- /dev/null
+++ b/seed/ts-sdk/server-sent-events/src/core/schemas/builders/bigint/index.ts
@@ -0,0 +1 @@
+export { bigint } from "./bigint";
diff --git a/seed/ts-sdk/server-sent-events/src/core/schemas/builders/index.ts b/seed/ts-sdk/server-sent-events/src/core/schemas/builders/index.ts
index 050cd2c4efb..65211f92522 100644
--- a/seed/ts-sdk/server-sent-events/src/core/schemas/builders/index.ts
+++ b/seed/ts-sdk/server-sent-events/src/core/schemas/builders/index.ts
@@ -1,3 +1,4 @@
+export * from "./bigint";
export * from "./date";
export * from "./enum";
export * from "./lazy";
diff --git a/seed/ts-sdk/server-sent-events/src/core/schemas/utils/getErrorMessageForIncorrectType.ts b/seed/ts-sdk/server-sent-events/src/core/schemas/utils/getErrorMessageForIncorrectType.ts
index 438012df418..1a5c31027ce 100644
--- a/seed/ts-sdk/server-sent-events/src/core/schemas/utils/getErrorMessageForIncorrectType.ts
+++ b/seed/ts-sdk/server-sent-events/src/core/schemas/utils/getErrorMessageForIncorrectType.ts
@@ -9,9 +9,13 @@ function getTypeAsString(value: unknown): string {
if (value === null) {
return "null";
}
+ if (value instanceof BigInt) {
+ return "BigInt";
+ }
switch (typeof value) {
case "string":
return `"${value}"`;
+ case "bigint":
case "number":
case "boolean":
case "undefined":
diff --git a/seed/ts-sdk/server-sent-events/src/version.ts b/seed/ts-sdk/server-sent-events/src/version.ts
new file mode 100644
index 00000000000..b643a3e3ea2
--- /dev/null
+++ b/seed/ts-sdk/server-sent-events/src/version.ts
@@ -0,0 +1 @@
+export const SDK_VERSION = "0.0.1";
diff --git a/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts b/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts
index e307b1589a7..1dc9be0cc0e 100644
--- a/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts
+++ b/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts
@@ -60,7 +60,7 @@ describe("Node18UniversalStreamWrapper", () => {
},
});
const stream = new Node18UniversalStreamWrapper(rawStream);
- const dest = new (await import("stream")).Writable({
+ const dest = new (await import("readable-stream")).Writable({
write(chunk, encoding, callback) {
expect(chunk.toString()).toEqual("test");
callback();
diff --git a/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts b/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts
index 861142a08b0..0c99d3b2655 100644
--- a/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts
+++ b/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts
@@ -2,7 +2,7 @@ import { NodePre18StreamWrapper } from "../../../../src/core/fetcher/stream-wrap
describe("NodePre18StreamWrapper", () => {
it("should set encoding to utf-8", async () => {
- const rawStream = (await import("stream")).Readable.from(["test", "test"]);
+ const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]);
const stream = new NodePre18StreamWrapper(rawStream);
const setEncodingSpy = jest.spyOn(stream, "setEncoding");
@@ -12,7 +12,7 @@ describe("NodePre18StreamWrapper", () => {
});
it("should register an event listener for readable", async () => {
- const rawStream = (await import("stream")).Readable.from(["test", "test"]);
+ const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]);
const stream = new NodePre18StreamWrapper(rawStream);
const onSpy = jest.spyOn(stream, "on");
@@ -22,7 +22,7 @@ describe("NodePre18StreamWrapper", () => {
});
it("should remove an event listener for data", async () => {
- const rawStream = (await import("stream")).Readable.from(["test", "test"]);
+ const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]);
const stream = new NodePre18StreamWrapper(rawStream);
const offSpy = jest.spyOn(stream, "off");
@@ -34,9 +34,9 @@ describe("NodePre18StreamWrapper", () => {
});
it("should write to dest when calling pipe to node writable stream", async () => {
- const rawStream = (await import("stream")).Readable.from(["test", "test"]);
+ const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]);
const stream = new NodePre18StreamWrapper(rawStream);
- const dest = new (await import("stream")).Writable({
+ const dest = new (await import("readable-stream")).Writable({
write(chunk, encoding, callback) {
expect(chunk.toString()).toEqual("test");
callback();
@@ -47,10 +47,10 @@ describe("NodePre18StreamWrapper", () => {
});
it("should write nothing when calling pipe and unpipe", async () => {
- const rawStream = (await import("stream")).Readable.from(["test", "test"]);
+ const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]);
const stream = new NodePre18StreamWrapper(rawStream);
const buffer: Uint8Array[] = [];
- const dest = new (await import("stream")).Writable({
+ const dest = new (await import("readable-stream")).Writable({
write(chunk, encoding, callback) {
buffer.push(chunk);
callback();
@@ -63,7 +63,7 @@ describe("NodePre18StreamWrapper", () => {
});
it("should destroy the stream", async () => {
- const rawStream = (await import("stream")).Readable.from(["test", "test"]);
+ const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]);
const stream = new NodePre18StreamWrapper(rawStream);
const destroySpy = jest.spyOn(stream, "destroy");
@@ -73,7 +73,7 @@ describe("NodePre18StreamWrapper", () => {
});
it("should pause the stream and resume", async () => {
- const rawStream = (await import("stream")).Readable.from(["test", "test"]);
+ const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]);
const stream = new NodePre18StreamWrapper(rawStream);
const pauseSpy = jest.spyOn(stream, "pause");
@@ -86,7 +86,7 @@ describe("NodePre18StreamWrapper", () => {
});
it("should read the stream", async () => {
- const rawStream = (await import("stream")).Readable.from(["test", "test"]);
+ const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]);
const stream = new NodePre18StreamWrapper(rawStream);
expect(await stream.read()).toEqual("test");
@@ -94,7 +94,7 @@ describe("NodePre18StreamWrapper", () => {
});
it("should read the stream as text", async () => {
- const rawStream = (await import("stream")).Readable.from(["test", "test"]);
+ const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]);
const stream = new NodePre18StreamWrapper(rawStream);
const data = await stream.text();
@@ -103,7 +103,7 @@ describe("NodePre18StreamWrapper", () => {
});
it("should read the stream as json", async () => {
- const rawStream = (await import("stream")).Readable.from([JSON.stringify({ test: "test" })]);
+ const rawStream = (await import("readable-stream")).Readable.from([JSON.stringify({ test: "test" })]);
const stream = new NodePre18StreamWrapper(rawStream);
const data = await stream.json();
@@ -112,7 +112,7 @@ describe("NodePre18StreamWrapper", () => {
});
it("should allow use with async iteratable stream", async () => {
- const rawStream = (await import("stream")).Readable.from(["test", "test"]);
+ const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]);
let data = "";
const stream = new NodePre18StreamWrapper(rawStream);
for await (const chunk of stream) {
diff --git a/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts b/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts
index aff7579e47a..17cf37a2f7f 100644
--- a/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts
+++ b/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts
@@ -21,7 +21,7 @@ describe("chooseStreamWrapper", () => {
});
it('should return a NodePre18StreamWrapper when RUNTIME.type is "node" and RUNTIME.parsedVersion is not null and RUNTIME.parsedVersion is less than 18', async () => {
- const stream = await import("stream");
+ const stream = await import("readable-stream");
const expected = new NodePre18StreamWrapper(new stream.Readable());
RUNTIME.type = "node";
diff --git a/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/webpack.test.ts b/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/webpack.test.ts
new file mode 100644
index 00000000000..557db6dc4ef
--- /dev/null
+++ b/seed/ts-sdk/server-sent-events/tests/unit/fetcher/stream-wrappers/webpack.test.ts
@@ -0,0 +1,38 @@
+import webpack from "webpack";
+
+describe("test env compatibility", () => {
+ test("webpack", () => {
+ return new Promise((resolve, reject) => {
+ webpack(
+ {
+ mode: "production",
+ entry: "./src/index.ts",
+ module: {
+ rules: [
+ {
+ test: /\.tsx?$/,
+ use: "ts-loader",
+ exclude: /node_modules/,
+ },
+ ],
+ },
+ resolve: {
+ extensions: [".tsx", ".ts", ".js"],
+ },
+ },
+ (err, stats) => {
+ try {
+ expect(err).toBe(null);
+ if (stats?.hasErrors()) {
+ console.log(stats?.toString());
+ }
+ expect(stats?.hasErrors()).toBe(false);
+ resolve();
+ } catch (error) {
+ reject(error);
+ }
+ }
+ );
+ });
+ }, 60_000);
+});
diff --git a/seed/ts-sdk/server-sent-events/tests/unit/zurg/bigint/bigint.test.ts b/seed/ts-sdk/server-sent-events/tests/unit/zurg/bigint/bigint.test.ts
new file mode 100644
index 00000000000..cf9935a749a
--- /dev/null
+++ b/seed/ts-sdk/server-sent-events/tests/unit/zurg/bigint/bigint.test.ts
@@ -0,0 +1,24 @@
+import { bigint } from "../../../../src/core/schemas/builders/bigint";
+import { itSchema } from "../utils/itSchema";
+import { itValidateJson, itValidateParse } from "../utils/itValidate";
+
+describe("bigint", () => {
+ itSchema("converts between raw string and parsed bigint", bigint(), {
+ raw: "123456789012345678901234567890123456789012345678901234567890",
+ parsed: BigInt("123456789012345678901234567890123456789012345678901234567890"),
+ });
+
+ itValidateParse("non-string", bigint(), 42, [
+ {
+ message: "Expected string. Received 42.",
+ path: [],
+ },
+ ]);
+
+ itValidateJson("non-bigint", bigint(), "hello", [
+ {
+ message: 'Expected bigint. Received "hello".',
+ path: [],
+ },
+ ]);
+});