v0.17.15
[0.17.15] - 2026-05-21
Fixed
-
alef-e2e/swift: guard
String.contains("")assertions with anisEmptyshort-circuit. Swift'sString.contains(_:)returnsfalsewhen the argument is the empty string, unlike every other language we target ("foo".contains("")istruein Python, JS, Ruby, etc.). Fixtures assertingcontains: ""on a response field (e.g.transcribereturning emptytextfor silent audio) therefore always failed withXCTAssertTrue failed - expected to contain:. The swift e2e codegen now emitsXCTAssertTrue({val}.isEmpty || {expr}.contains({val}), …)for the non-array, non-traversalcontainscases, preserving the original semantic ("any string trivially contains the empty string") while leaving non-empty containment checks unchanged. Surfaced on liter-llm Swift e2e —TranscribeTests.testEdgeTranscribeEmptyAudio. (crates/alef-e2e/src/codegen/swift.rs) -
alef-backend-swift: fall back to a
Value::Stringwrapper whenfrom_strfails for string-like fields. Thedefault_field_string_like_serde.jinja/default_field_string_like_optional_serde.jinjatemplates wrapped rawStringconstructor arguments inserde_json::from_str(&v).ok()→from_value(v).ok(). For inputs that are NOT valid JSON tokens — bare values liketts-1,whisper-1,dall-e-3—from_strsilently returnsErr, theif let Okbranch is skipped, and the target field stays at the struct'sDefaultvalue (emptyString). The high-level SwiftintoRust()therefore round-tripped fixtures through a swift-bridge constructor that silently zeroed themodelfield, causing the Rust client to reject the call withbad request: model must not be empty. Both templates now first tryfrom_str; on parse failure they fall back toserde_json::Value::String(v)and thenfrom_value, so raw strings deserialize correctly into plainString(and string-like enum) fields while structured JSON (objects / arrays / string-like enums encoded as JSON strings) continues to work. Surfaced on liter-llm Swift e2e — 13 failures across SpeechTests, TranscribeTests, ImageGenerateTests where the test request JSON serializedmodelas a bare string. (crates/alef-backend-swift/templates/default_field_string_like_serde.jinja,crates/alef-backend-swift/templates/default_field_string_like_optional_serde.jinja) -
alef-backend-csharp: emit separate JsonSerializationOptions for FFI parameter serialization. The C# generator used a single
JsonSerializerOptionswithDefaultIgnoreCondition.WhenWritingDefaultfor both deserializing FFI responses and serializing input parameters (likeConversionOptions) to pass to Rust. When a test explicitly set an option to false/0/null, the serializer skipped writing that field — Rust received incomplete JSON and applied defaults, overwriting the caller's intent. Surfaced on html-to-markdown C# e2e: 3 tests failed because options likepreprocessing.enabled=falsewere omitted from serialization. Added a secondJsonSerializationOptions(withoutWhenWritingDefault) used when serializing Named parameters and config objects in wrappers and streaming methods; deserialization continues to use the originalJsonOptionsfor sparse response handling. (crates/alef-backend-csharp/src/gen_bindings/types.rs,methods.rs, and templates) -
alef-backend-java: change ObjectMapper serialization inclusion from NON_NULL to ALWAYS. When Java code called Rust via FFI, the ObjectMapper's
JsonInclude.Include.NON_NULLconfiguration silently omitted all null-valued fields from the JSON sent tohtm_conversion_options_from_json. The Rust deserializer then saw an incomplete JSON object and applied defaults for all missing fields, overwriting any explicitly-set false/0/null values from Java. Effectively, any test setting an option to its "falsy" default (e.g.,highlightStyle=null,includeDocumentStructure=false) would be ignored because those fields disappeared from the JSON. Changed toJsonInclude.Include.ALWAYSso all fields serialize regardless of value, ensuring the Rust FFI layer sees exactly what the Java caller set. Surfaced on html-to-markdown Java e2e tests — 33 tests failed because option setters likewithHighlightStyle()were being overwritten by Rust defaults; 7 additional tests failed with NullPointerException onresult.document()becauseincludeDocumentStructure=falsewas omitted. (crates/alef-backend-java/templates/helper_object_mapper.jinja) -
alef-e2e/csharp: append
Asyncsuffix to non-overridden async method names.render_test_method/render_chat_stream_test_methodderived the C# call site fromcall_config.function.to_upper_camel_case()without appending the conventionalAsyncsuffix that the C# generator emits on async wrappers. Whenfunction: "chat"andasync: truewere declared inalef.toml(with no explicitcsharp.functionoverride), the test invokedclient.Chat(...)but the C# binding only exposedclient.ChatAsync(...). Now the suffix is appended automatically whencall_config.r#asyncis true and the name doesn't already end inAsync; explicitcs_overrides.functionoverrides still win. (crates/alef-e2e/src/codegen/csharp.rs) -
alef-e2e/zig: implement the
equalsassertion path for string results. The Zig assertion renderer matched onresult_is_simplestrings (null,true,false, …) but had no arm forequals, so fixtures asserting an exact string value silently emitted notry testing.expect…line — the generated test compiled and passed without ever checking the result. Added anequalsarm that lowers the JSON value viajson_to_zigand emitstry testing.expectEqualStrings(expected, result.?). (crates/alef-e2e/src/codegen/zig.rs) -
alef-e2e/swift: prevent UInt() wrapping of floating-point numeric literals in comparisons. The
swift_numeric_literal_castfunction unconditionally wrapped all numeric method-call accessor comparisons (e.g.,result.relevanceScore()) inUInt(...), assuming all opaque swift-bridge methods return UInt. However, some accessors returnDouble(e.g.,relevanceScore() -> Double), causing compilation errors when comparing against float literals like0.9. Fixed by detecting float-valued literals (containing.) and skipping theUInt()wrapper; integer literals continue to be wrapped to match UInt-returning accessors. Also applied the numeric-literal-cast function consistently across all numeric comparison assertions (equals, greater_than, less_than, etc.) instead of only some, and refactored the optional-field coalesce paths to use the cast function. Surfaced on liter-llm Swift e2e — RerankTests and similar fixtures with float field assertions. (crates/alef-e2e/src/codegen/swift.rs) -
alef-e2e: treat inline
href="/…"anchors in fixture HTML as ahas_host_root_routetrigger.Fixture::has_host_root_route()previously only fired for/robots*//sitemap*paths or 3xx Location / Refresh /<meta refresh>redirects targeting/…. Multi-page crawl fixtures that traversed between mock responses via plain<a href="/page1">links did not qualify, so language-specific e2e codegens (Python, Node, Ruby, …) emitted the sharedMOCK_SERVER_URL + /fixtures/<id>URL rather than the per-fixtureMOCK_SERVER_<FIXTURE_ID>env-var lookup. The crawl engine then resolved linked/pagepaths against the host root and 404'd against the namespaced shared listener. Detectinghref="//href='/in anybody_inlinemirrors the runtime mock-server'shas_inline_host_linkpredicate (60deac3) so compile-time and runtime decisions stay in sync. Surfaced on kreuzcrawl Python e2e —test_crawl_concurrent_depthgot 1 page instead of 3. (crates/alef-e2e/src/fixture.rs) -
alef-e2e/php: honor
options_via = "from_json"when constructing visitor options. When a fixture used a visitor callback and the PHP e2e call override specifiedoptions_via = "from_json", the test code generator unconditionally emittedConversionOptions::builder()->visitor($visitor)->build(), which does not exist. The correct behavior is to honoroptions_viaand useConversionOptions::from_json(json_encode(['visitor' => $visitor]))instead. This caused 54 php e2e test failures in html-to-markdown (ConversionTests, OptionsTests, VisitorTests) with "Call to undefined method ConversionOptions::builder()". (crates/alef-e2e/src/codegen/php.rs)
Changed
- maintenance: record a clean full-project
prek run --all-filesverification.