-
Notifications
You must be signed in to change notification settings - Fork 81
Description
liquid-rust version: 0.26.9
rust version: rustc 1.82.0 (f6e511eec 2024-10-15)
OS: Debian 12 (bookworm)
I believe I have found a bug: liquid-rust
seems to equate false
and nil
.
My reasons for believing this is a bug are as follows:
- The observed behavior differs from that of Shopify's gem.
- The observed behavior surprises me.
- The observed behavior is inconsistent (as the code below demonstrates).
I've included Ruby and Rust sample code along with their outputs. I apologize for the long MWEs, but I thought clarity was more important than brevity in this case.
Also, please note that the examples below use copied and pasted templates between Ruby and Rust to ensure consistency; I haven't even touched the template indentation to match both languages' styles.
Shopify (Ruby, using gem liquid
version 5.5.1)
require "liquid"
def false_first
raw_template = \
"{% if x == true -%}
if: true
{%- elsif x == false -%}
if: false
{%- elsif x == nil -%}
if: nil
{%- else -%}
if: else
{%- endif -%}
; {% case x -%}
{%- when true -%}
case: true
{%- when false -%}
case: false
{%- when nil -%}
case: nil
{%- else -%}
case: else
{%- endcase %}"
template = Liquid::Template.parse(raw_template)
puts "=== Comparison order: true, false, nil ==="
puts "[true] #{template.render('x' => true)}"
puts "[false] #{template.render('x' => false)}"
puts "[nil] #{template.render('x' => nil)}"
puts
end
def nil_first
raw_template = \
"{% if x == true -%}
if: true
{%- elsif x == nil -%}
if: nil
{%- elsif x == false -%}
if: false
{%- else -%}
if: else
{%- endif -%}
; {% case x -%}
{%- when true -%}
case: true
{%- when nil -%}
case: nil
{%- when false -%}
case: false
{%- else -%}
case: else
{%- endcase %}"
template = Liquid::Template.parse(raw_template)
puts "=== Comparison order: true, nil, false ==="
puts "[true] #{template.render('x' => true)}"
puts "[false] #{template.render('x' => false)}"
puts "[nil] #{template.render('x' => nil)}"
puts
end
false_first
nil_first
Ruby output
=== Comparison order: true, false, nil ===
[true] if: true; case: true
[false] if: false; case: false
[nil] if: nil; case: nil
=== Comparison order: true, nil, false ===
[true] if: true; case: true
[false] if: false; case: false
[nil] if: nil; case: nil
Rust (using liquid-rust
version 0.26.9):
fn main() {
false_first();
nil_first();
}
fn false_first() {
let raw_template =
"{% if x == true -%}
if: true
{%- elsif x == false -%}
if: false
{%- elsif x == nil -%}
if: nil
{%- else -%}
if: else
{%- endif -%}
; {% case x -%}
{%- when true -%}
case: true
{%- when false -%}
case: false
{%- when nil -%}
case: nil
{%- else -%}
case: else
{%- endcase %}";
let template = liquid::ParserBuilder::with_stdlib()
.build()
.unwrap()
.parse(raw_template)
.unwrap();
println!("=== Comparison order: true, false, nil ===");
println!("[true] {}", template.render(&liquid::object!({ "x": true })).unwrap());
println!("[false] {}", template.render(&liquid::object!({ "x": false })).unwrap());
println!("[Some(true)] {}", template.render(&liquid::object!({ "x": Some(true) })).unwrap());
println!("[Some(false)] {}", template.render(&liquid::object!({ "x": Some(false) })).unwrap());
println!("[None] {}", template.render(&liquid::object!({ "x": None::<bool> })).unwrap());
println!();
}
fn nil_first() {
let raw_template =
"{% if x == true -%}
if: true
{%- elsif x == nil -%}
if: nil
{%- elsif x == false -%}
if: false
{%- else -%}
if: else
{%- endif -%}
; {% case x -%}
{%- when true -%}
case: true
{%- when nil -%}
case: nil
{%- when false -%}
case: false
{%- else -%}
case: else
{%- endcase %}";
let template = liquid::ParserBuilder::with_stdlib()
.build()
.unwrap()
.parse(raw_template)
.unwrap();
println!("=== Comparison order: true, nil, false ===");
println!("[true] {}", template.render(&liquid::object!({ "x": true })).unwrap());
println!("[false] {}", template.render(&liquid::object!({ "x": false })).unwrap());
println!("[Some(true)] {}", template.render(&liquid::object!({ "x": Some(true) })).unwrap());
println!("[Some(false)] {}", template.render(&liquid::object!({ "x": Some(false) })).unwrap());
println!("[None] {}", template.render(&liquid::object!({ "x": None::<bool> })).unwrap());
println!();
}
Rust output
=== Comparison order: true, false, nil ===
[true] if: true; case: true
[false] if: false; case: false
[Some(true)] if: true; case: true
[Some(false)] if: false; case: false
[None] if: false; case: false
=== Comparison order: true, nil, false ===
[true] if: true; case: true
[false] if: nil; case: nil
[Some(true)] if: true; case: true
[Some(false)] if: nil; case: nil
[None] if: nil; case: nil
Expected output
=== Comparison order: true, false, nil ===
[true] if: true; case: true
[false] if: false; case: false
[Some(true)] if: true; case: true
[Some(false)] if: false; case: false
[None] if: nil; case: nil
=== Comparison order: true, nil, false ===
[true] if: true; case: true
[false] if: false; case: false
[Some(true)] if: true; case: true
[Some(false)] if: false; case: false
[None] if: nil; case: nil
Use case
I have an optional Boolean value and need to distinguish Some(true)
, Some(false)
, and None
.
Workaround
I map the value to a string literal before passing it to liquid::object!
, and then I perform string comparison in my template, e.g. "nil"
instead of nil
.