libcamera: yaml-parser: Differentiate between empty and empty string
diff mbox series

Message ID 20240916222834.464460-1-stefan.klug@ideasonboard.com
State Accepted
Headers show
Series
  • libcamera: yaml-parser: Differentiate between empty and empty string
Related show

Commit Message

Stefan Klug Sept. 16, 2024, 10:28 p.m. UTC
When accessing a nonexistent key on a dict the YamlObject returns an
empty element. This element can happily be casted to a string which is
unexpected. For example the following statement:
yamlDict["nonexistant"].get<string>("default") is expected to return
"default" but actually returns "". Fix this by introducing a empty type
to distinguish between an empty YamlObject and a YamlObject of type
value containing an empty string. For completeness add an isEmpty()
function to be able to test for that type.

Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
---
 include/libcamera/internal/yaml_parser.h |  5 +++++
 src/libcamera/yaml_parser.cpp            | 20 ++++++++++++++------
 test/yaml-parser.cpp                     |  6 ++++++
 3 files changed, 25 insertions(+), 6 deletions(-)

Comments

Barnabás Pőcze Sept. 17, 2024, 1:45 p.m. UTC | #1
Hi


2024. szeptember 17., kedd 00:28 keltezéssel, Stefan Klug <stefan.klug@ideasonboard.com> írta:

> When accessing a nonexistent key on a dict the YamlObject returns an
> empty element. This element can happily be casted to a string which is
> unexpected. For example the following statement:
> yamlDict["nonexistant"].get<string>("default") is expected to return
> "default" but actually returns "". Fix this by introducing a empty type
> to distinguish between an empty YamlObject and a YamlObject of type
> value containing an empty string. For completeness add an isEmpty()
> function to be able to test for that type.
> 
> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> ---
>  include/libcamera/internal/yaml_parser.h |  5 +++++
>  src/libcamera/yaml_parser.cpp            | 20 ++++++++++++++------
>  test/yaml-parser.cpp                     |  6 ++++++
>  3 files changed, 25 insertions(+), 6 deletions(-)
> 
> diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h
> index 16708e488d88..33c3a96fed47 100644
> --- a/include/libcamera/internal/yaml_parser.h
> +++ b/include/libcamera/internal/yaml_parser.h
> @@ -159,6 +159,10 @@ public:
>  	{
>  		return type_ == Type::Dictionary;
>  	}
> +	bool isEmpty() const
> +	{
> +		return type_ == Type::Empty;
> +	}

I think `constexpr explicit operator bool() const` would be a good addition as well.
Then you can just do e.g.

  if (auto y = x["something"]) {
    // ...
  }


> 
>  	std::size_t size() const;
> 
> @@ -212,6 +216,7 @@ private:
>  		Dictionary,
>  		List,
>  		Value,
> +		Empty,
>  	};
> [...]


Regards,
Barnabás Pőcze
Paul Elder Sept. 17, 2024, 2:12 p.m. UTC | #2
On Tue, Sep 17, 2024 at 12:28:20AM +0200, Stefan Klug wrote:
> When accessing a nonexistent key on a dict the YamlObject returns an
> empty element. This element can happily be casted to a string which is
> unexpected. For example the following statement:
> yamlDict["nonexistant"].get<string>("default") is expected to return

s/nonexistant/nonexistent/

> "default" but actually returns "". Fix this by introducing a empty type

s/ a / an /

> to distinguish between an empty YamlObject and a YamlObject of type
> value containing an empty string. For completeness add an isEmpty()
> function to be able to test for that type.

Ooh that's a smart solution.

> 
> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> ---
>  include/libcamera/internal/yaml_parser.h |  5 +++++
>  src/libcamera/yaml_parser.cpp            | 20 ++++++++++++++------
>  test/yaml-parser.cpp                     |  6 ++++++
>  3 files changed, 25 insertions(+), 6 deletions(-)
> 
> diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h
> index 16708e488d88..33c3a96fed47 100644
> --- a/include/libcamera/internal/yaml_parser.h
> +++ b/include/libcamera/internal/yaml_parser.h
> @@ -159,6 +159,10 @@ public:
>  	{
>  		return type_ == Type::Dictionary;
>  	}
> +	bool isEmpty() const
> +	{
> +		return type_ == Type::Empty;
> +	}
>  
>  	std::size_t size() const;
>  
> @@ -212,6 +216,7 @@ private:
>  		Dictionary,
>  		List,
>  		Value,
> +		Empty,
>  	};
>  
>  	template<typename T>
> diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp
> index 8b6a403898be..eb72529136f7 100644
> --- a/src/libcamera/yaml_parser.cpp
> +++ b/src/libcamera/yaml_parser.cpp
> @@ -38,12 +38,12 @@ static const YamlObject empty;
>   * \brief A class representing the tree structure of the YAML content
>   *
>   * The YamlObject class represents the tree structure of YAML content. A
> - * YamlObject can be a dictionary or list of YamlObjects or a value if a tree
> - * leaf.
> + * YamlObject can be empty, a dictionary or list of YamlObjects or a value if a
> + * tree leaf.
>   */
>  
>  YamlObject::YamlObject()
> -	: type_(Type::Value)
> +	: type_(Type::Empty)
>  {
>  }
>  
> @@ -70,6 +70,13 @@ YamlObject::~YamlObject() = default;
>   * \return True if the YamlObject is a dictionary, false otherwise
>   */
>  
> +/**
> + * \fn YamlObject::isEmpty()
> + * \brief Return whether the YamlObject is a empty

s/ a / an /

> + *
> + * \return True if the YamlObject is empty, false otherwise
> + */
> +
>  /**
>   * \fn YamlObject::size()
>   * \brief Retrieve the number of elements in a dictionary or list YamlObject
> @@ -443,7 +450,8 @@ template std::optional<std::vector<Size>> YamlObject::getList<Size>() const;
>   *
>   * This function retrieves an element of the YamlObject. Only YamlObject
>   * instances of List type associate elements with index, calling this function
> - * on other types of instances is invalid and results in undefined behaviour.
> + * on other types of instances or with an invalid index results in an empty
> + * object.
>   *
>   * \return The YamlObject as an element of the list
>   */
> @@ -480,8 +488,8 @@ bool YamlObject::contains(const std::string &key) const
>   *
>   * This function retrieve a member of a YamlObject by name. Only YamlObject
>   * instances of Dictionary type associate elements with names, calling this
> - * function on other types of instances is invalid and results in undefined
> - * behaviour.
> + * function on other types of instances or with a non existant key results in an

s/non existant/nonexistent/

Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>

> + * empty object.
>   *
>   * \return The YamlObject corresponding to the \a key member
>   */
> diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp
> index 81c829834667..b8ae0b4c11f8 100644
> --- a/test/yaml-parser.cpp
> +++ b/test/yaml-parser.cpp
> @@ -514,6 +514,12 @@ protected:
>  			i++;
>  		}
>  
> +		/* Test access to nonexistent member */
> +		if (dictObj["nonexistent"].get<std::string>("default") != "default") {
> +			cerr << "Accessing nonexistent dict fails to return default" << std::endl;
> +			return TestFail;
> +		}
> +
>  		/* Make sure utils::map_keys() works on the adapter. */
>  		(void)utils::map_keys(dictObj.asDict());
>  
> -- 
> 2.43.0
>
Laurent Pinchart Sept. 17, 2024, 3:09 p.m. UTC | #3
On Tue, Sep 17, 2024 at 04:12:45PM +0200, Paul Elder wrote:
> On Tue, Sep 17, 2024 at 12:28:20AM +0200, Stefan Klug wrote:
> > When accessing a nonexistent key on a dict the YamlObject returns an
> > empty element. This element can happily be casted to a string which is

s/casted/cast/

> > unexpected. For example the following statement:
> > yamlDict["nonexistant"].get<string>("default") is expected to return

unexpected. For example the following statement:

yamlDict["nonexistent"].get<string>("default")

is expected to return

> s/nonexistant/nonexistent/
> 
> > "default" but actually returns "". Fix this by introducing a empty type
> 
> s/ a / an /
> 
> > to distinguish between an empty YamlObject and a YamlObject of type
> > value containing an empty string. For completeness add an isEmpty()
> > function to be able to test for that type.
> 
> Ooh that's a smart solution.

Indeed. Normally I'd worry that this could break things in unexpected
ways, but we have unit tests :-)

> > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> > ---
> >  include/libcamera/internal/yaml_parser.h |  5 +++++
> >  src/libcamera/yaml_parser.cpp            | 20 ++++++++++++++------
> >  test/yaml-parser.cpp                     |  6 ++++++
> >  3 files changed, 25 insertions(+), 6 deletions(-)
> > 
> > diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h
> > index 16708e488d88..33c3a96fed47 100644
> > --- a/include/libcamera/internal/yaml_parser.h
> > +++ b/include/libcamera/internal/yaml_parser.h
> > @@ -159,6 +159,10 @@ public:
> >  	{
> >  		return type_ == Type::Dictionary;
> >  	}
> > +	bool isEmpty() const
> > +	{
> > +		return type_ == Type::Empty;
> > +	}
> >  
> >  	std::size_t size() const;
> >  
> > @@ -212,6 +216,7 @@ private:
> >  		Dictionary,
> >  		List,
> >  		Value,
> > +		Empty,
> >  	};
> >  
> >  	template<typename T>
> > diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp
> > index 8b6a403898be..eb72529136f7 100644
> > --- a/src/libcamera/yaml_parser.cpp
> > +++ b/src/libcamera/yaml_parser.cpp
> > @@ -38,12 +38,12 @@ static const YamlObject empty;
> >   * \brief A class representing the tree structure of the YAML content
> >   *
> >   * The YamlObject class represents the tree structure of YAML content. A
> > - * YamlObject can be a dictionary or list of YamlObjects or a value if a tree
> > - * leaf.
> > + * YamlObject can be empty, a dictionary or list of YamlObjects or a value if a

 * YamlObject can be empty, a dictionary or list of YamlObjects, or a value if a

> > + * tree leaf.
> >   */
> >  
> >  YamlObject::YamlObject()
> > -	: type_(Type::Value)
> > +	: type_(Type::Empty)
> >  {
> >  }
> >  
> > @@ -70,6 +70,13 @@ YamlObject::~YamlObject() = default;
> >   * \return True if the YamlObject is a dictionary, false otherwise
> >   */
> >  
> > +/**
> > + * \fn YamlObject::isEmpty()
> > + * \brief Return whether the YamlObject is a empty
> 
> s/ a / an /
> 
> > + *
> > + * \return True if the YamlObject is empty, false otherwise
> > + */
> > +
> >  /**
> >   * \fn YamlObject::size()
> >   * \brief Retrieve the number of elements in a dictionary or list YamlObject
> > @@ -443,7 +450,8 @@ template std::optional<std::vector<Size>> YamlObject::getList<Size>() const;
> >   *
> >   * This function retrieves an element of the YamlObject. Only YamlObject
> >   * instances of List type associate elements with index, calling this function
> > - * on other types of instances is invalid and results in undefined behaviour.
> > + * on other types of instances or with an invalid index results in an empty
> > + * object.
> >   *
> >   * \return The YamlObject as an element of the list
> >   */
> > @@ -480,8 +488,8 @@ bool YamlObject::contains(const std::string &key) const
> >   *
> >   * This function retrieve a member of a YamlObject by name. Only YamlObject
> >   * instances of Dictionary type associate elements with names, calling this
> > - * function on other types of instances is invalid and results in undefined
> > - * behaviour.
> > + * function on other types of instances or with a non existant key results in an
> 
> s/non existant/nonexistent/
> 
> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
> 
> > + * empty object.
> >   *
> >   * \return The YamlObject corresponding to the \a key member
> >   */
> > diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp
> > index 81c829834667..b8ae0b4c11f8 100644
> > --- a/test/yaml-parser.cpp
> > +++ b/test/yaml-parser.cpp
> > @@ -514,6 +514,12 @@ protected:
> >  			i++;
> >  		}
> >  
> > +		/* Test access to nonexistent member */

s/member/member./

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> > +		if (dictObj["nonexistent"].get<std::string>("default") != "default") {
> > +			cerr << "Accessing nonexistent dict fails to return default" << std::endl;
> > +			return TestFail;
> > +		}
> > +
> >  		/* Make sure utils::map_keys() works on the adapter. */
> >  		(void)utils::map_keys(dictObj.asDict());
> >
Stefan Klug Sept. 18, 2024, 9:25 a.m. UTC | #4
Hi Barnabás,

On Tue, Sep 17, 2024 at 01:45:13PM +0000, Barnabás Pőcze wrote:
> Hi
> 
> 
> 2024. szeptember 17., kedd 00:28 keltezéssel, Stefan Klug <stefan.klug@ideasonboard.com> írta:
> 
> > When accessing a nonexistent key on a dict the YamlObject returns an
> > empty element. This element can happily be casted to a string which is
> > unexpected. For example the following statement:
> > yamlDict["nonexistant"].get<string>("default") is expected to return
> > "default" but actually returns "". Fix this by introducing a empty type
> > to distinguish between an empty YamlObject and a YamlObject of type
> > value containing an empty string. For completeness add an isEmpty()
> > function to be able to test for that type.
> > 
> > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> > ---
> >  include/libcamera/internal/yaml_parser.h |  5 +++++
> >  src/libcamera/yaml_parser.cpp            | 20 ++++++++++++++------
> >  test/yaml-parser.cpp                     |  6 ++++++
> >  3 files changed, 25 insertions(+), 6 deletions(-)
> > 
> > diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h
> > index 16708e488d88..33c3a96fed47 100644
> > --- a/include/libcamera/internal/yaml_parser.h
> > +++ b/include/libcamera/internal/yaml_parser.h
> > @@ -159,6 +159,10 @@ public:
> >  	{
> >  		return type_ == Type::Dictionary;
> >  	}
> > +	bool isEmpty() const
> > +	{
> > +		return type_ == Type::Empty;
> > +	}
> 
> I think `constexpr explicit operator bool() const` would be a good addition as well.
> Then you can just do e.g.
> 
>   if (auto y = x["something"]) {
>     // ...
>   }

Thanks for the suggestion. I'll add that. One question though. Why the
constexpr? I can't come up with a practical case were that can be
resolved at compiletime.

Best regards,
Stefan

> 
> 
> > 
> >  	std::size_t size() const;
> > 
> > @@ -212,6 +216,7 @@ private:
> >  		Dictionary,
> >  		List,
> >  		Value,
> > +		Empty,
> >  	};
> > [...]
> 
> 
> Regards,
> Barnabás Pőcze
Barnabás Pőcze Sept. 18, 2024, 1:07 p.m. UTC | #5
Hi


2024. szeptember 18., szerda 11:25 keltezéssel, Stefan Klug <stefan.klug@ideasonboard.com> írta:

> Hi Barnabás,
> 
> On Tue, Sep 17, 2024 at 01:45:13PM +0000, Barnabás Pőcze wrote:
> > Hi
> >
> >
> > 2024. szeptember 17., kedd 00:28 keltezéssel, Stefan Klug <stefan.klug@ideasonboard.com> írta:
> >
> > > When accessing a nonexistent key on a dict the YamlObject returns an
> > > empty element. This element can happily be casted to a string which is
> > > unexpected. For example the following statement:
> > > yamlDict["nonexistant"].get<string>("default") is expected to return
> > > "default" but actually returns "". Fix this by introducing a empty type
> > > to distinguish between an empty YamlObject and a YamlObject of type
> > > value containing an empty string. For completeness add an isEmpty()
> > > function to be able to test for that type.
> > >
> > > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> > > ---
> > >  include/libcamera/internal/yaml_parser.h |  5 +++++
> > >  src/libcamera/yaml_parser.cpp            | 20 ++++++++++++++------
> > >  test/yaml-parser.cpp                     |  6 ++++++
> > >  3 files changed, 25 insertions(+), 6 deletions(-)
> > >
> > > diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h
> > > index 16708e488d88..33c3a96fed47 100644
> > > --- a/include/libcamera/internal/yaml_parser.h
> > > +++ b/include/libcamera/internal/yaml_parser.h
> > > @@ -159,6 +159,10 @@ public:
> > >  	{
> > >  		return type_ == Type::Dictionary;
> > >  	}
> > > +	bool isEmpty() const
> > > +	{
> > > +		return type_ == Type::Empty;
> > > +	}
> >
> > I think `constexpr explicit operator bool() const` would be a good addition as well.
> > Then you can just do e.g.
> >
> >   if (auto y = x["something"]) {
> >     // ...
> >   }
> 
> Thanks for the suggestion. I'll add that. One question though. Why the
> constexpr? I can't come up with a practical case were that can be
> resolved at compiletime.

I did it mostly automatically, so I guess you're right. Habit is second nature...


Regards,
Barnabás Pőcze

Patch
diff mbox series

diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h
index 16708e488d88..33c3a96fed47 100644
--- a/include/libcamera/internal/yaml_parser.h
+++ b/include/libcamera/internal/yaml_parser.h
@@ -159,6 +159,10 @@  public:
 	{
 		return type_ == Type::Dictionary;
 	}
+	bool isEmpty() const
+	{
+		return type_ == Type::Empty;
+	}
 
 	std::size_t size() const;
 
@@ -212,6 +216,7 @@  private:
 		Dictionary,
 		List,
 		Value,
+		Empty,
 	};
 
 	template<typename T>
diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp
index 8b6a403898be..eb72529136f7 100644
--- a/src/libcamera/yaml_parser.cpp
+++ b/src/libcamera/yaml_parser.cpp
@@ -38,12 +38,12 @@  static const YamlObject empty;
  * \brief A class representing the tree structure of the YAML content
  *
  * The YamlObject class represents the tree structure of YAML content. A
- * YamlObject can be a dictionary or list of YamlObjects or a value if a tree
- * leaf.
+ * YamlObject can be empty, a dictionary or list of YamlObjects or a value if a
+ * tree leaf.
  */
 
 YamlObject::YamlObject()
-	: type_(Type::Value)
+	: type_(Type::Empty)
 {
 }
 
@@ -70,6 +70,13 @@  YamlObject::~YamlObject() = default;
  * \return True if the YamlObject is a dictionary, false otherwise
  */
 
+/**
+ * \fn YamlObject::isEmpty()
+ * \brief Return whether the YamlObject is a empty
+ *
+ * \return True if the YamlObject is empty, false otherwise
+ */
+
 /**
  * \fn YamlObject::size()
  * \brief Retrieve the number of elements in a dictionary or list YamlObject
@@ -443,7 +450,8 @@  template std::optional<std::vector<Size>> YamlObject::getList<Size>() const;
  *
  * This function retrieves an element of the YamlObject. Only YamlObject
  * instances of List type associate elements with index, calling this function
- * on other types of instances is invalid and results in undefined behaviour.
+ * on other types of instances or with an invalid index results in an empty
+ * object.
  *
  * \return The YamlObject as an element of the list
  */
@@ -480,8 +488,8 @@  bool YamlObject::contains(const std::string &key) const
  *
  * This function retrieve a member of a YamlObject by name. Only YamlObject
  * instances of Dictionary type associate elements with names, calling this
- * function on other types of instances is invalid and results in undefined
- * behaviour.
+ * function on other types of instances or with a non existant key results in an
+ * empty object.
  *
  * \return The YamlObject corresponding to the \a key member
  */
diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp
index 81c829834667..b8ae0b4c11f8 100644
--- a/test/yaml-parser.cpp
+++ b/test/yaml-parser.cpp
@@ -514,6 +514,12 @@  protected:
 			i++;
 		}
 
+		/* Test access to nonexistent member */
+		if (dictObj["nonexistent"].get<std::string>("default") != "default") {
+			cerr << "Accessing nonexistent dict fails to return default" << std::endl;
+			return TestFail;
+		}
+
 		/* Make sure utils::map_keys() works on the adapter. */
 		(void)utils::map_keys(dictObj.asDict());