diff options
| author | tv <tv@krebsco.de> | 2026-11-02 11:07:46 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-02 11:07:46 +0100 |
| commit | a7b8b4d6543c4c780334df1c823d2e2f10d9e1bf (patch) | |
| tree | 12cf1ad634ed128c40bfd9b6c9b9283dc81b3908 /src/document | |
| parent | 694661d7477e4bd1e8d7ec96dcc37742d1b5ca3f (diff) | |
api: drop $float as JSON number is OK
Diffstat (limited to 'src/document')
| -rw-r--r-- | src/document/store.rs | 53 | ||||
| -rw-r--r-- | src/document/typed.rs | 14 |
2 files changed, 40 insertions, 27 deletions
diff --git a/src/document/store.rs b/src/document/store.rs index 9075032..1a235b2 100644 --- a/src/document/store.rs +++ b/src/document/store.rs @@ -676,14 +676,27 @@ pub(crate) fn write_val_path( Ok(writing.retyped) } +/// Which values a write writes as the type of what they are written over (`stuck`). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum Sticky { + /// None: the value says its type (a type's own media type, a cast). + No, + /// A number without a fraction over a float: typed JSON says every other type + /// itself, but not a float's, as JSON numbers don't keep `2.0` apart from `2` in + /// every language. + Floats, + /// Plain JSON, which has fewer types than Automerge: where it is the JSON form of a + /// value of the type there (a string that reads as a timestamp, as base64, a number + /// without a fraction over a float, an unsigned integer or a counter, a string over + /// a text). + All, +} + /// How a write treats the types of what it writes over. pub(crate) struct Writing { - /// Types are sticky: the value comes as plain JSON, which has fewer types than - /// Automerge, and where it is the JSON form of a value of the type there (a string - /// that reads as a timestamp, as base64, a number without a fraction over a float, - /// an unsigned integer or a counter, a string over a text), it is written as that - /// type (`stuck`). Typed JSON and a type's own media type say the type themselves. - pub(crate) sticky: bool, + /// Types are sticky: what the value is written as where it is the JSON form of a + /// value of the type there. + pub(crate) sticky: Sticky, /// How sticky timestamps are written in JSON (`typed::TimestampFormat::json`). pub(crate) timestamps: TimestampFormat, /// Whether the write may change the type of what it writes over; if not, one that @@ -695,10 +708,10 @@ pub(crate) struct Writing { impl Writing { pub(crate) fn sticky(timestamps: TimestampFormat) -> Self { - Self::new(true, timestamps, true) + Self::new(Sticky::All, timestamps, true) } - pub(crate) fn new(sticky: bool, timestamps: TimestampFormat, retype: bool) -> Self { + pub(crate) fn new(sticky: Sticky, timestamps: TimestampFormat, retype: bool) -> Self { Self { sticky, timestamps, @@ -760,11 +773,23 @@ fn kind_of(held: &Value<'_>) -> Kind { } /// `value` written as the type of `held`, where it is a JSON form of a value of it -/// (`Writing::sticky`); None where it isn't (it is written as it is). -fn stuck(held: &Value<'_>, value: &Val, timestamps: TimestampFormat) -> Option<Val> { +/// as `sticky` says (`Writing::sticky`); None where it isn't (it is written as it is). +fn stuck( + held: &Value<'_>, + value: &Val, + sticky: Sticky, + timestamps: TimestampFormat, +) -> Option<Val> { let Val::Scalar(scalar) = value else { return None; }; + let over_float = matches!(held, Value::Scalar(held) if matches!(held.as_ref(), ScalarValue::F64(_))) + && matches!(scalar, ScalarValue::Int(_) | ScalarValue::Uint(_)); + match sticky { + Sticky::No => return None, + Sticky::Floats if !over_float => return None, + Sticky::Floats | Sticky::All => {} + } let held = match held { Value::Object(ObjType::Text) => { return match scalar { @@ -843,11 +868,7 @@ fn update_of<'v>( let Some((held, _)) = holds.first() else { return Ok((Update::Anew, Cow::Borrowed(value))); }; - let value = match writing - .sticky - .then(|| stuck(held, value, writing.timestamps)) - .flatten() - { + let value = match stuck(held, value, writing.sticky, writing.timestamps) { Some(stuck) => Cow::Owned(stuck), None => Cow::Borrowed(value), }; @@ -1083,7 +1104,7 @@ pub(crate) fn cast_path( .as_ref() .ok_or_else(|| not_castable(format!("{} is cast to nothing", held.kind.name())))?; let cast = typed::cast(held.kind, value, to, timestamps).map_err(not_castable)?; - write_val_path(doc, path, &cast, Writing::new(false, timestamps, true)) + write_val_path(doc, path, &cast, Writing::new(Sticky::No, timestamps, true)) } /// Inserts `value` into a list: `path` ends in the index it goes to (the items from diff --git a/src/document/typed.rs b/src/document/typed.rs index 7f6e0cc..def06df 100644 --- a/src/document/typed.rs +++ b/src/document/typed.rs @@ -224,9 +224,9 @@ pub(crate) struct JsonForm { // Typed JSON is plain JSON but for what plain JSON shows as another type: a single-key // object whose key is a tag names it. `{"$uint": 5}`, `{"$counter": 5}`, // `{"$timestamp": "2026-11-01T21:50:20.345Z"}` (in the timestamp format asked), -// `{"$bytes": "<base64>"}`, `{"$float": 2}` (a float without a fraction, which JSON -// numbers don't keep apart from integers in every language), `{"$text": "…"}` (an -// Automerge text). A map whose only key starts with `$` is `{"$map": {…}}`, so that +// `{"$bytes": "<base64>"}`, `{"$text": "…"}` (an Automerge text). A float is a number, +// as in plain JSON; a number without a fraction written over a float stays one +// (`store::Sticky::Floats`). A map whose only key starts with `$` is `{"$map": {…}}`, so that // nothing else reads as a tag. /// A map's single key that is a tag, or would be taken for one. @@ -270,9 +270,6 @@ pub(crate) fn scalar_to_json(scalar: &ScalarValue, form: JsonForm) -> Option<Jso return Some(tagged("$timestamp", form.timestamps.json(*t))); } ScalarValue::Bytes(b) => return Some(tagged("$bytes", BASE64.encode(b).into())), - ScalarValue::F64(f) if f.fract() == 0.0 => { - return Some(tagged("$float", serde_json::Number::from_f64(*f)?.into())); - } _ => {} } } @@ -590,11 +587,6 @@ impl Val { })?)) } "$counter" => Val::Scalar(ScalarValue::counter(integer(value)?)), - "$float" => Val::Scalar(ScalarValue::F64( - value - .as_f64() - .ok_or_else(|| format!("$float: not a number: {value}"))?, - )), "$timestamp" => { let text = match value { JsonValue::String(text) => text.clone(), |
