Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature::field returns Result<Option<FieldValue>> #134

Merged
merged 2 commits into from
Jan 22, 2021
Merged

Feature::field returns Result<Option<FieldValue>> #134

merged 2 commits into from
Jan 22, 2021

Conversation

dead10ck
Copy link
Contributor

@dead10ck dead10ck commented Jan 3, 2021

Fixes #132

@michaelkirk
Copy link
Member

This seems like a useful change, though it's likely to break a lot of existing code right? Maybe worth a note in CHANGES.md to help people migrating?

@dead10ck
Copy link
Contributor Author

dead10ck commented Jan 4, 2021

Yes, it's likely to break a lot of code. I do think it's beneficial, however. I myself thought of this because I found myself writing code like

fn get_property_string(
    key: &str,
    feature: &mut gdal::vector::Feature,
) -> Result<Option<String>, Box<dyn Error + Send + Sync>> {
    let s: String = feature
        .field(key)?
        .into_string()
        .ok_or(format!("missing key: {}", key))?;

    if &s[..] == "" {
        Ok(None)
    } else {
        Ok(Some(s))
    }
}

to get around it. This just so happened to work for me, but I imagine there are use cases where 0 and "" are valid values that need to be distinguished from null.

I'll add a note to the change log.

CHANGES.md Outdated
legitimate values which happen to be default value, for example, an Integer
field that is absent (null) from a 0, which can be a valid value. After this
change, if a field is null, `None` is returned, rather than the default
value.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there likely to be people for whom the old behavior, of getting the default value, is desirable?

If so, after this change, how will they restore that behavior?
Will they need to use snippets like: .unwrap_or_default(0) . unwrap_or_default("")? or is there a way to say something like "field.default_value()"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(To be clear, I think your change is good overall, just trying to think of any pain we can save a minority of people)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's right, if they had dataset.field("some_field")?.into_int() before, they could do dataset.field("some_field")?.unwrap_or(FieldValue::Integer(0)).into_int(). Although it would surprise me what kind of use case there would be where people wouldn't be surprised by nulls turning into 0 or "". Probably the more likely case is people aren't aware that nulls silently get turned into real values.

Copy link
Member

@michaelkirk michaelkirk Jan 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably the more likely case is people aren't aware that nulls silently get turned into real values.

Yeah, that seems likely. 👍

it would surprise me what kind of use case there would be where people wouldn't be surprised by nulls turning into 0 or "".

I've never personally used this feature, but apparently you can get/set other defaults for fields: https://gdal.org/doxygen/classOGRFieldDefn.html#ac4210fa7c6f10ed090a5558224447cfa

Maybe out of scope for this PR, but since you're thinking about it, what's your opinion on exposing an API leveraging that functionality like:

let foo: Result<FieldValue> = dataset.field_default("some_field");

Then people could exactly mirror the previous behavior with:

let foo: Result<FieldValue> = dataset.field("some_field")?.unwrap_or_else(dataset.field_default("some_field"))

and maybe offered succinctly as:

let foo: Result<FieldValue> = dataset.field_value_or_default("some_field");

edit: fixed code

Copy link
Contributor Author

@dead10ck dead10ck Jan 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes a lot of sense to me. Having a field_value_or_default function is clear and descriptive, and exposes useful functionality from OGR.

And since you ask, I was actually starting to think about whether it would be possible to do away with the FieldValue type completely and just use generics. I haven't fully fleshed out the details, but I was thinking it should be possible to just make the field generic on the return type, so you could just do

let str_var: String = dataset.field("string_field")?.unwrap();
let int_var: i32 = dataset.field("int_field")?.unwrap();
etc...

I think it could be done if we made a Field type that mirrors the OGRFieldDfn type, then impl TryFrom<str> for Field<String/i32/etc>, and then the signature of field could be something like

fn field<T, D>(field_dfn: D) -> Result<Option<T>> where D: TryInto<Field<T>>

The Field type could have that set_default method on it too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there anything else you wanted for this PR? Did you want me to add that example code snippet for getting a default value?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the PR looks fine. We will wait a few days for other PRs with breaking changes before we release a new version.
Could you add docs to the field_from_id method and include that example snippet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 🙂

@jdroenner
Copy link
Member

uh clippy is not happy :( could you check what is wrong?

Currently, Feature::field returns Result<FieldValue>, but fields can be
null. When this is the case, the default value for the FieldValue
variant is returned. This makes it difficult to distinguish e.g. a
legitimate Integer 0 value from the absence of a value.

Instead, return a Result<Option<FieldValue>> so that null fields can be
distinguished from default values.
@dead10ck
Copy link
Contributor Author

Oh sorry, I did not run the tests with --all-features, so I missed a breakage in the examples. I've fixed it, and a couple other issues with the examples. I ran all of the example binaries and made sure they worked.

@jdroenner jdroenner merged commit c1c2720 into georust:master Jan 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature::field should return Result<Option<FieldValue>>
3 participants