changeset 60:8a4bcb3dc3a6

Replace exceptions throughout the JSON-handling and adapter code with string-arg error handling. No longer need exception handling enabled in Emscripten (with its consequent runtime overhead - though we still need to check whether this error handling regime is actually faster).
author Chris Cannam <c.cannam@qmul.ac.uk>
date Tue, 20 Sep 2016 16:35:47 +0100
parents 77833938f0f8
children 0ea374ea96a2
files Makefile bits/CountingPluginHandleMapper.h bits/DefaultPluginOutputIdMapper.h bits/PluginHandleMapper.h bits/PluginOutputIdMapper.h bits/PreservingPluginHandleMapper.h bits/PreservingPluginOutputIdMapper.h json/VampJson.h json/json-test.cpp utilities/json-cli.cpp utilities/json-to-capnp.cpp utilities/vampipe-convert.cpp
diffstat 12 files changed, 449 insertions(+), 758 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Mon Sep 19 15:52:38 2016 +0100
+++ b/Makefile	Tue Sep 20 16:35:47 2016 +0100
@@ -6,7 +6,7 @@
 
 #!!! todo: proper dependencies
 
-all:	bin/vamp-json-cli bin/vamp-json-to-capnp bin/vampipe-convert bin/vampipe-server
+all:	bin/vampipe-convert bin/vampipe-server
 
 bin/vampipe-convert: o/vampipe-convert.o o/json11.o o/vamp.capnp.o
 	c++ $(CXXFLAGS) $^ -o $@ $(LDFLAGS)
@@ -14,12 +14,6 @@
 bin/vampipe-server: o/vampipe-server.o o/vamp.capnp.o
 	c++ $(CXXFLAGS) $^ -o $@ $(LDFLAGS)
 
-bin/vamp-json-to-capnp:	o/json-to-capnp.o o/json11.o
-	c++ $(CXXFLAGS) $^ -o $@ $(LDFLAGS)
-
-bin/vamp-json-cli:	o/json-cli.o o/json11.o
-	c++ $(CXXFLAGS) $^ -o $@ $(LDFLAGS)
-
 capnproto/vamp.capnp.h:	capnproto/vamp.capnp
 	capnp compile $< -oc++
 
@@ -35,15 +29,8 @@
 o/vampipe-server.o:	utilities/vampipe-server.cpp capnproto/vamp.capnp.h capnproto/VampnProto.h 
 	c++ $(CXXFLAGS) $(INCFLAGS) -c $< -o $@
 
-o/json-to-capnp.o:	utilities/json-to-capnp.cpp capnproto/vamp.capnp.h capnproto/VampnProto.h json/VampJson.h
-	c++ $(CXXFLAGS) $(INCFLAGS) -c $< -o $@
-
-o/json-cli.o:	utilities/json-cli.cpp json/VampJson.h
-	c++ $(CXXFLAGS) $(INCFLAGS) -c $< -o $@
-
 test:	all
-	VAMP_PATH=./vamp-plugin-sdk/examples test/test-json-cli.sh
-	VAMP_PATH=./vamp-plugin-sdk/examples test/test-json-to-capnp.sh
+	test/test-vampipe-server.sh
 
 clean:
 	rm -f */*.o capnproto/vamp.capnp.h capnproto/vamp.capnp.c++
--- a/bits/CountingPluginHandleMapper.h	Mon Sep 19 15:52:38 2016 +0100
+++ b/bits/CountingPluginHandleMapper.h	Tue Sep 20 16:35:47 2016 +0100
@@ -44,13 +44,13 @@
 
 namespace vampipe {
 
-//!!! NB not thread-safe at present, should it be?
 class CountingPluginHandleMapper : public PluginHandleMapper
 {
 public:
     CountingPluginHandleMapper() : m_nextHandle(1) { }
 
     void addPlugin(Vamp::Plugin *p) {
+        if (!p) return;
 	if (m_rplugins.find(p) == m_rplugins.end()) {
 	    Handle h = m_nextHandle++;
 	    m_plugins[h] = p;
@@ -61,9 +61,7 @@
     }
 
     void removePlugin(Handle h) {
-	if (m_plugins.find(h) == m_plugins.end()) {
-	    throw NotFound();
-	}
+	if (m_plugins.find(h) == m_plugins.end()) return;
 	Vamp::Plugin *p = m_plugins[h];
         m_outputMappers.erase(h);
 	m_plugins.erase(h);
@@ -74,54 +72,57 @@
 	m_rplugins.erase(p);
     }
     
-    Handle pluginToHandle(Vamp::Plugin *p) const {
+    Handle pluginToHandle(Vamp::Plugin *p) const noexcept {
 	if (m_rplugins.find(p) == m_rplugins.end()) {
-	    throw NotFound();
+            return INVALID_HANDLE;
 	}
 	return m_rplugins.at(p);
     }
     
-    Vamp::Plugin *handleToPlugin(Handle h) const {
+    Vamp::Plugin *handleToPlugin(Handle h) const noexcept {
 	if (m_plugins.find(h) == m_plugins.end()) {
-	    throw NotFound();
+            return nullptr;
 	}
 	return m_plugins.at(h);
     }
 
     const std::shared_ptr<PluginOutputIdMapper> pluginToOutputIdMapper
-    (Vamp::Plugin *p) const {
-        // pluginToHandle checks the plugin has been registered with us
-        return m_outputMappers.at(pluginToHandle(p));
+    (Vamp::Plugin *p) const noexcept {
+        return handleToOutputIdMapper(pluginToHandle(p));
     }
 
     const std::shared_ptr<PluginOutputIdMapper> handleToOutputIdMapper
-    (Handle h) const {
-	if (m_plugins.find(h) == m_plugins.end()) {
-	    throw NotFound();
+    (Handle h) const noexcept {
+	if (h != INVALID_HANDLE &&
+            m_outputMappers.find(h) != m_outputMappers.end()) {
+            return m_outputMappers.at(h);
+        } else {
+	    return {};
 	}
-        return m_outputMappers.at(h);
     }
 
-    bool isConfigured(Handle h) const {
+    bool isConfigured(Handle h) const noexcept {
+        if (h == INVALID_HANDLE) return false;
 	return m_configuredPlugins.find(h) != m_configuredPlugins.end();
     }
 
     void markConfigured(Handle h, int channelCount, int blockSize) {
+        if (h == INVALID_HANDLE) return;
 	m_configuredPlugins.insert(h);
 	m_channelCounts[h] = channelCount;
 	m_blockSizes[h] = blockSize;
     }
 
-    int getChannelCount(Handle h) const {
+    int getChannelCount(Handle h) const noexcept {
 	if (m_channelCounts.find(h) == m_channelCounts.end()) {
-	    throw NotFound();
+	    return 0;
 	}
 	return m_channelCounts.at(h);
     }
 
-    int getBlockSize(Handle h) const {
+    int getBlockSize(Handle h) const noexcept {
 	if (m_blockSizes.find(h) == m_blockSizes.end()) {
-	    throw NotFound();
+            return 0;
 	}
 	return m_blockSizes.at(h);
     }
--- a/bits/DefaultPluginOutputIdMapper.h	Mon Sep 19 15:52:38 2016 +0100
+++ b/bits/DefaultPluginOutputIdMapper.h	Tue Sep 20 16:35:47 2016 +0100
@@ -49,19 +49,18 @@
 	}
     }
 
-    virtual int idToIndex(std::string outputId) const {
+    virtual int idToIndex(std::string outputId) const noexcept {
 	int n = int(m_ids.size());
 	for (int i = 0; i < n; ++i) {
 	    if (outputId == m_ids[i]) {
 		return i;
 	    }
 	}
-	//!!! todo: this should in fact throw, or otherwise return an error
-	return 0;
+	return -1;
     }
 
-    virtual std::string indexToId(int index) const {
-	//!!! todo: this should in fact throw, or otherwise return an error
+    virtual std::string indexToId(int index) const noexcept {
+        if (index < 0 || size_t(index) >= m_ids.size()) return "";
 	return m_ids[index];
     }
 
--- a/bits/PluginHandleMapper.h	Mon Sep 19 15:52:38 2016 +0100
+++ b/bits/PluginHandleMapper.h	Tue Sep 20 16:35:47 2016 +0100
@@ -42,28 +42,56 @@
 
 namespace vampipe {
 
+/**
+ * Convert plugin pointers to handles within some scope defined by the
+ * individual PluginHandleMapper implementation.
+ *
+ * The special handle 0 and the NULL plugin pointer are both used to
+ * represent "not found" and will be returned in any case where an
+ * unknown handle or plugin is requested.
+ *
+ * Note that the handle type must be representable as a JSON number,
+ * hence the use of a 32-bit rather than 64-bit int.
+ *
+ * This interface also includes methods for obtaining a
+ * PluginOutputIdMapper, \see PluginOutputIdMapper.
+ */
+
 class PluginHandleMapper
 {
-    // NB the handle type must fit in a JSON number
-    
 public:
     typedef int32_t Handle;
+    const Handle INVALID_HANDLE = 0;
 
-    virtual ~PluginHandleMapper() { }
-    
-    class NotFound : virtual public std::runtime_error {
-    public:
-        NotFound() : runtime_error("plugin or handle not found in mapper") { }
-    };
-    
-    virtual Handle pluginToHandle(Vamp::Plugin *) const = 0; // may throw NotFound
-    virtual Vamp::Plugin *handleToPlugin(Handle)  const = 0; // may throw NotFound
+    virtual ~PluginHandleMapper() noexcept { }
 
+    /**
+     * Look up and return the handle for a given plugin pointer.
+     * If the given pointer is null or not known, return INVALID_HANDLE.
+     */
+    virtual Handle pluginToHandle(Vamp::Plugin *) const noexcept = 0;
+
+    /**
+     * Look up and return the plugin for a given handle.
+     * If the given handle is INVALID_HANDLE or not known, return nullptr.
+     */
+    virtual Vamp::Plugin *handleToPlugin(Handle)  const noexcept = 0;
+
+    /**
+     * Return a shared pointer to a PluginOutputIdMapper
+     * implementation for the given plugin pointer.  If the given
+     * pointer is null or not known, return the null shared_ptr.
+     */
     virtual const std::shared_ptr<PluginOutputIdMapper> pluginToOutputIdMapper
-    (Vamp::Plugin *p) const = 0; // may throw NotFound
+    (Vamp::Plugin *p) const noexcept = 0;
 
+    /**
+     * Return a shared pointer to a PluginOutputIdMapper
+     * implementation for the given plugin handle.  If the given
+     * handle is INVALID_HANDLE or not known, return the null shared_ptr.
+     */
     virtual const std::shared_ptr<PluginOutputIdMapper> handleToOutputIdMapper
-    (Handle h) const = 0; // may throw NotFound
+    (Handle h) const noexcept = 0;
 };
 
 }
--- a/bits/PluginOutputIdMapper.h	Mon Sep 19 15:52:38 2016 +0100
+++ b/bits/PluginOutputIdMapper.h	Tue Sep 20 16:35:47 2016 +0100
@@ -42,14 +42,23 @@
 
 namespace vampipe {
 
-//!!! doc interface
 class PluginOutputIdMapper
 {
 public:
     virtual ~PluginOutputIdMapper() { }
-    
-    virtual int idToIndex(std::string outputId) const = 0;
-    virtual std::string indexToId(int index) const = 0;
+
+    /**
+     * Return the index of the given output id in the plugin. The
+     * first output has index 0. If the given output id is unknown,
+     * return -1.
+     */
+    virtual int idToIndex(std::string outputId) const noexcept = 0;
+
+    /**
+     * Return the id of the output with the given index in the
+     * plugin. If the index is out of range, return the empty string.
+     */
+    virtual std::string indexToId(int index) const noexcept = 0;
 };
 
 }
--- a/bits/PreservingPluginHandleMapper.h	Mon Sep 19 15:52:38 2016 +0100
+++ b/bits/PreservingPluginHandleMapper.h	Tue Sep 20 16:35:47 2016 +0100
@@ -54,30 +54,34 @@
         m_plugin(0),
         m_omapper(std::make_shared<PreservingPluginOutputIdMapper>()) { }
 
-    virtual Handle pluginToHandle(Vamp::Plugin *p) const {
+    virtual Handle pluginToHandle(Vamp::Plugin *p) const noexcept {
+        if (!p) return INVALID_HANDLE;
 	if (p == m_plugin) return m_handle;
 	else {
 	    std::cerr << "PreservingPluginHandleMapper: p = " << p
 		      << " differs from saved m_plugin " << m_plugin
 		      << " (not returning saved handle " << m_handle << ")"
 		      << std::endl;
-	    throw NotFound();
+            return INVALID_HANDLE;
 	}
     }
 
-    virtual Vamp::Plugin *handleToPlugin(Handle h) const {
+    virtual Vamp::Plugin *handleToPlugin(Handle h) const noexcept {
+        if (h == INVALID_HANDLE) return nullptr;
 	m_handle = h;
 	m_plugin = reinterpret_cast<Vamp::Plugin *>(h);
 	return m_plugin;
     }
 
     virtual const std::shared_ptr<PluginOutputIdMapper> pluginToOutputIdMapper
-    (Vamp::Plugin *) const {
+    (Vamp::Plugin *p) const noexcept {
+        if (!p) return {};
         return m_omapper;
     }
         
     virtual const std::shared_ptr<PluginOutputIdMapper> handleToOutputIdMapper
-    (Handle h) const {
+    (Handle h) const noexcept {
+        if (h == INVALID_HANDLE) return {};
         return m_omapper;
     }
     
--- a/bits/PreservingPluginOutputIdMapper.h	Mon Sep 19 15:52:38 2016 +0100
+++ b/bits/PreservingPluginOutputIdMapper.h	Tue Sep 20 16:35:47 2016 +0100
@@ -49,7 +49,7 @@
 public:
     PreservingPluginOutputIdMapper() { }
 
-    virtual int idToIndex(std::string outputId) const {
+    virtual int idToIndex(std::string outputId) const noexcept {
 	int n = int(m_ids.size());
 	int i = 0;
 	while (i < n) {
@@ -62,8 +62,8 @@
 	return i;
     }
 
-    virtual std::string indexToId(int index) const {
-	//!!! todo: this should in fact throw, or otherwise return an error
+    virtual std::string indexToId(int index) const noexcept {
+        if (index < 0 || size_t(index) >= m_ids.size()) return "";
 	return m_ids[index];
     }
 
--- a/json/VampJson.h	Mon Sep 19 15:52:38 2016 +0100
+++ b/json/VampJson.h	Tue Sep 20 16:35:47 2016 +0100
@@ -38,7 +38,6 @@
 #include <vector>
 #include <string>
 #include <sstream>
-#include <stdexcept>
 
 #include <json11/json11.hpp>
 #include <base-n/include/basen.hpp>
@@ -56,7 +55,25 @@
  * Convert the structures laid out in the Vamp SDK classes into JSON
  * (and back again) following the schema in the vamp-json-schema
  * project repo.
+ *
+ * Functions with names starting "from" convert from a Vamp SDK object
+ * to JSON output. Most of them return a json11::Json object, with a
+ * few exceptions for low-level utilities that return a string. These
+ * functions succeed all of the time.
+ *
+ * Functions with names starting "to" convert to a Vamp SDK object
+ * from JSON input. These functions all accept a json11::Json object
+ * as first argument, with a few exceptions for low-level utilities
+ * that accept a string. These functions all accept a string reference
+ * as a final argument and return an error string through it if the
+ * conversion fails. If conversion fails the return value is
+ * undefined, and any returned object may be incomplete or
+ * invalid. Callers should check for an empty error string (indicating
+ * success) before using the returned value.
  */
+
+//!!! todo: convert pmapper to err style
+
 class VampJson
 {
 public:
@@ -88,10 +105,9 @@
         Base64
     };
     
-    class Failure : virtual public std::runtime_error {
-    public:
-        Failure(std::string s) : runtime_error(s) { }
-    };
+    static bool failed(const std::string &err) {
+        return err != "";
+    }
     
     template <typename T>
     static json11::Json
@@ -105,12 +121,14 @@
 
     template <typename T>
     static void
-    toBasicDescriptor(json11::Json j, T &t) {
+    toBasicDescriptor(json11::Json j, T &t, std::string &err) {
         if (!j.is_object()) {
-            throw Failure("object expected for basic descriptor content");
+            err = "object expected for basic descriptor content";
+            return;
         }
         if (!j["identifier"].is_string()) {
-            throw Failure("string expected for identifier");
+            err = "string expected for identifier";
+            return;
         }
         t.identifier = j["identifier"].string_value();
         t.name = j["name"].string_value();
@@ -128,7 +146,7 @@
 
     template <typename T>
     static bool
-    toValueExtents(json11::Json j, T &t) {
+    toValueExtents(json11::Json j, T &t, std::string &err) {
         if (j["extents"].is_null()) {
             return false;
         } else if (j["extents"].is_object()) {
@@ -138,10 +156,12 @@
                 t.maxValue = j["extents"]["max"].number_value();
                 return true;
             } else {
-                throw Failure("numbers expected for min and max");
+                err = "numbers expected for min and max";
+                return false;
             }
         } else {
-            throw Failure("object expected for extents (if present)");
+            err = "object expected for extents (if present)";
+            return false;
         }
     }
 
@@ -154,11 +174,12 @@
     }
 
     static Vamp::RealTime
-    toRealTime(json11::Json j) {
+    toRealTime(json11::Json j, std::string &err) {
         json11::Json sec = j["s"];
         json11::Json nsec = j["n"];
         if (!sec.is_number() || !nsec.is_number()) {
-            throw Failure("invalid Vamp::RealTime object " + j.dump());
+            err = "invalid Vamp::RealTime object " + j.dump();
+            return {};
         }
         return Vamp::RealTime(sec.int_value(), nsec.int_value());
     }
@@ -177,7 +198,7 @@
     }
 
     static Vamp::Plugin::OutputDescriptor::SampleType
-    toSampleType(std::string text) {
+    toSampleType(std::string text, std::string &err) {
         if (text == "OneSamplePerStep") {
             return Vamp::Plugin::OutputDescriptor::OneSamplePerStep;
         } else if (text == "FixedSampleRate") {
@@ -185,7 +206,8 @@
         } else if (text == "VariableSampleRate") {
             return Vamp::Plugin::OutputDescriptor::VariableSampleRate;
         } else {
-            throw Failure("invalid sample type string: " + text);
+            err = "invalid sample type string: " + text;
+            return Vamp::Plugin::OutputDescriptor::OneSamplePerStep;
         }
     }
 
@@ -213,21 +235,25 @@
     }
     
     static Vamp::Plugin::OutputDescriptor
-    toOutputDescriptor(json11::Json j) {
+    toOutputDescriptor(json11::Json j, std::string &err) {
 
         Vamp::Plugin::OutputDescriptor od;
         if (!j.is_object()) {
-            throw Failure("object expected for output descriptor");
+            err = "object expected for output descriptor";
+            return {};
         }
     
-        toBasicDescriptor(j["basic"], od);
+        toBasicDescriptor(j["basic"], od, err);
+        if (failed(err)) return {};
 
         od.unit = j["unit"].string_value();
 
-        od.sampleType = toSampleType(j["sampleType"].string_value());
+        od.sampleType = toSampleType(j["sampleType"].string_value(), err);
+        if (failed(err)) return {};
 
         if (!j["sampleRate"].is_number()) {
-            throw Failure("number expected for sample rate");
+            err = "number expected for sample rate";
+            return {};
         }
         od.sampleRate = j["sampleRate"].number_value();
         od.hasDuration = j["hasDuration"].bool_value();
@@ -237,7 +263,8 @@
             od.binCount = j["binCount"].int_value();
             for (auto &n: j["binNames"].array_items()) {
                 if (!n.is_string()) {
-                    throw Failure("string expected for bin name");
+                    err = "string expected for bin name";
+                    return {};
                 }
                 od.binNames.push_back(n.string_value());
             }
@@ -245,7 +272,9 @@
             od.hasFixedBinCount = false;
         }
 
-        bool extentsPresent = toValueExtents(j, od);
+        bool extentsPresent = toValueExtents(j, od, err);
+        if (failed(err)) return {};
+        
         od.hasKnownExtents = extentsPresent;
 
         if (j["quantizeStep"].is_number()) {
@@ -276,24 +305,29 @@
     }
 
     static Vamp::PluginBase::ParameterDescriptor
-    toParameterDescriptor(json11::Json j) {
+    toParameterDescriptor(json11::Json j, std::string &err) {
 
         Vamp::PluginBase::ParameterDescriptor pd;
         if (!j.is_object()) {
-            throw Failure("object expected for parameter descriptor");
+            err = "object expected for parameter descriptor";
+            return {};
         }
     
-        toBasicDescriptor(j["basic"], pd);
+        toBasicDescriptor(j["basic"], pd, err);
+        if (failed(err)) return {};
 
         pd.unit = j["unit"].string_value();
 
-        bool extentsPresent = toValueExtents(j, pd);
+        bool extentsPresent = toValueExtents(j, pd, err);
+        if (failed(err)) return {};
         if (!extentsPresent) {
-            throw Failure("extents must be present in parameter descriptor");
+            err = "extents must be present in parameter descriptor";
+            return {};
         }
     
         if (!j["defaultValue"].is_number()) {
-            throw Failure("number expected for default value");
+            err = "number expected for default value";
+            return {};
         }
     
         pd.defaultValue = j["defaultValue"].number_value();
@@ -301,7 +335,8 @@
         pd.valueNames.clear();
         for (auto &n: j["valueNames"].array_items()) {
             if (!n.is_string()) {
-                throw Failure("string expected for value name");
+                err = "string expected for value name";
+                return {};
             }
             pd.valueNames.push_back(n.string_value());
         }
@@ -328,7 +363,7 @@
     }
 
     static std::vector<float>
-    toFloatBuffer(std::string encoded) {
+    toFloatBuffer(std::string encoded, std::string & /* err */) {
         std::string decoded;
         bn::decode_b64(encoded.begin(), encoded.end(), back_inserter(decoded));
         const float *buffer = reinterpret_cast<const float *>(decoded.c_str());
@@ -363,23 +398,26 @@
     }
 
     static Vamp::Plugin::Feature
-    toFeature(json11::Json j,
-              BufferSerialisation &serialisation) {
+    toFeature(json11::Json j, BufferSerialisation &serialisation, std::string &err) {
 
         Vamp::Plugin::Feature f;
         if (!j.is_object()) {
-            throw Failure("object expected for feature");
+            err = "object expected for feature";
+            return {};
         }
         if (j["timestamp"].is_object()) {
-            f.timestamp = toRealTime(j["timestamp"]);
+            f.timestamp = toRealTime(j["timestamp"], err);
+            if (failed(err)) return {};
             f.hasTimestamp = true;
         }
         if (j["duration"].is_object()) {
-            f.duration = toRealTime(j["duration"]);
+            f.duration = toRealTime(j["duration"], err);
+            if (failed(err)) return {};
             f.hasDuration = true;
         }
         if (j["b64values"].is_string()) {
-            f.values = toFloatBuffer(j["b64values"].string_value());
+            f.values = toFloatBuffer(j["b64values"].string_value(), err);
+            if (failed(err)) return {};
             serialisation = BufferSerialisation::Base64;
         } else if (j["values"].is_array()) {
             for (auto v : j["values"].array_items()) {
@@ -409,14 +447,16 @@
 
     static Vamp::Plugin::FeatureList
     toFeatureList(json11::Json j,
-                  BufferSerialisation &serialisation) {
+                  BufferSerialisation &serialisation, std::string &err) {
 
         Vamp::Plugin::FeatureList fl;
         if (!j.is_array()) {
-            throw Failure("array expected for feature list");
+            err = "array expected for feature list";
+            return {};
         }
         for (const json11::Json &fj : j.array_items()) {
-            fl.push_back(toFeature(fj, serialisation));
+            fl.push_back(toFeature(fj, serialisation, err));
+            if (failed(err)) return {};
         }
         return fl;
     }
@@ -424,18 +464,22 @@
     static Vamp::Plugin::FeatureSet
     toFeatureSet(json11::Json j,
                  const PluginOutputIdMapper &omapper,
-                 BufferSerialisation &serialisation) {
+                 BufferSerialisation &serialisation,
+                 std::string &err) {
 
         Vamp::Plugin::FeatureSet fs;
         if (!j.is_object()) {
-            throw Failure("object expected for feature set");
+            err = "object expected for feature set";
+            return {};
         }
         for (auto &entry : j.object_items()) {
             int n = omapper.idToIndex(entry.first);
             if (fs.find(n) != fs.end()) {
-                throw Failure("duplicate numerical index for output");
+                err = "duplicate numerical index for output";
+                return {};
             }
-            fs[n] = toFeatureList(entry.second, serialisation);
+            fs[n] = toFeatureList(entry.second, serialisation, err);
+            if (failed(err)) return {};
         }
         return fs;
     }
@@ -453,14 +497,15 @@
     }
 
     static Vamp::Plugin::InputDomain
-    toInputDomain(std::string text) {
+    toInputDomain(std::string text, std::string &err) {
 
         if (text == "TimeDomain") {
             return Vamp::Plugin::TimeDomain;
         } else if (text == "FrequencyDomain") {
             return Vamp::Plugin::FrequencyDomain;
         } else {
-            throw Failure("invalid input domain string: " + text);
+            err = "invalid input domain string: " + text;
+            return {};
         }
     }
 
@@ -502,98 +547,108 @@
     }
 
     static Vamp::HostExt::PluginStaticData
-    toPluginStaticData(json11::Json j) {
+    toPluginStaticData(json11::Json j, std::string &err) {
 
-        std::string err;
         if (!j.has_shape({
                     { "pluginKey", json11::Json::STRING },
                     { "pluginVersion", json11::Json::NUMBER },
                     { "minChannelCount", json11::Json::NUMBER },
                     { "maxChannelCount", json11::Json::NUMBER },
                     { "inputDomain", json11::Json::STRING }}, err)) {
-            throw Failure("malformed plugin static data: " + err);
+
+            err = "malformed plugin static data: " + err;
+
+        } else if (!j["basicOutputInfo"].is_array()) {
+
+            err = "array expected for basic output info";
+
+        } else if (!j["maker"].is_null() &&
+                   !j["maker"].is_string()) {
+
+            err = "string expected for maker";
+
+        } else if (!j["copyright"].is_null() &&
+                   !j["copyright"].is_string()) {
+            err = "string expected for copyright";
+
+        } else if (!j["category"].is_null() &&
+                   !j["category"].is_array()) {
+
+            err = "array expected for category";
+
+        } else if (!j["parameters"].is_null() &&
+                   !j["parameters"].is_array()) {
+
+            err = "array expected for parameters";
+
+        } else if (!j["programs"].is_null() &&
+                   !j["programs"].is_array()) {
+
+            err = "array expected for programs";
+
+        } else if (!j["inputDomain"].is_null() &&
+                   !j["inputDomain"].is_string()) {
+
+            err = "string expected for inputDomain";
+
+        } else if (!j["basicOutputInfo"].is_null() &&
+                   !j["basicOutputInfo"].is_array()) {
+            
+            err = "array expected for basicOutputInfo";
+
+        } else {
+
+            Vamp::HostExt::PluginStaticData psd;
+
+            psd.pluginKey = j["pluginKey"].string_value();
+
+            toBasicDescriptor(j["basic"], psd.basic, err);
+            if (failed(err)) return {};
+
+            psd.maker = j["maker"].string_value();
+            psd.copyright = j["copyright"].string_value();
+            psd.pluginVersion = j["pluginVersion"].int_value();
+
+            for (const auto &c : j["category"].array_items()) {
+                if (!c.is_string()) {
+                    err = "strings expected in category array";
+                    return {};
+                }
+                psd.category.push_back(c.string_value());
+            }
+
+            psd.minChannelCount = j["minChannelCount"].int_value();
+            psd.maxChannelCount = j["maxChannelCount"].int_value();
+
+            for (const auto &p : j["parameters"].array_items()) {
+                auto pd = toParameterDescriptor(p, err);
+                if (failed(err)) return {};
+                psd.parameters.push_back(pd);
+            }
+
+            for (const auto &p : j["programs"].array_items()) {
+                if (!p.is_string()) {
+                    err = "strings expected in programs array";
+                    return {};
+                }
+                psd.programs.push_back(p.string_value());
+            }
+
+            psd.inputDomain = toInputDomain(j["inputDomain"].string_value(), err);
+            if (failed(err)) return {};
+
+            for (const auto &bo : j["basicOutputInfo"].array_items()) {
+                Vamp::HostExt::PluginStaticData::Basic b;
+                toBasicDescriptor(bo, b, err);
+                if (failed(err)) return {};
+                psd.basicOutputInfo.push_back(b);
+            }
+            
+            return psd;
         }
 
-        if (!j["basicOutputInfo"].is_array()) {
-            throw Failure("array expected for basic output info");
-        }
-
-        if (!j["maker"].is_null() &&
-            !j["maker"].is_string()) {
-            throw Failure("string expected for maker");
-        }
-        
-        if (!j["copyright"].is_null() &&
-            !j["copyright"].is_string()) {
-            throw Failure("string expected for copyright");
-        }
-
-        if (!j["category"].is_null() &&
-            !j["category"].is_array()) {
-            throw Failure("array expected for category");
-        }
-
-        if (!j["parameters"].is_null() &&
-            !j["parameters"].is_array()) {
-            throw Failure("array expected for parameters");
-        }
-
-        if (!j["programs"].is_null() &&
-            !j["programs"].is_array()) {
-            throw Failure("array expected for programs");
-        }
-
-        if (!j["inputDomain"].is_null() &&
-            !j["inputDomain"].is_string()) {
-            throw Failure("string expected for inputDomain");
-        }
-
-        if (!j["basicOutputInfo"].is_null() &&
-            !j["basicOutputInfo"].is_array()) {
-            throw Failure("array expected for basicOutputInfo");
-        }
-
-        Vamp::HostExt::PluginStaticData psd;
-
-        psd.pluginKey = j["pluginKey"].string_value();
-
-        toBasicDescriptor(j["basic"], psd.basic);
-
-        psd.maker = j["maker"].string_value();
-        psd.copyright = j["copyright"].string_value();
-        psd.pluginVersion = j["pluginVersion"].int_value();
-
-        for (const auto &c : j["category"].array_items()) {
-            if (!c.is_string()) {
-                throw Failure("strings expected in category array");
-            }
-            psd.category.push_back(c.string_value());
-        }
-
-        psd.minChannelCount = j["minChannelCount"].int_value();
-        psd.maxChannelCount = j["maxChannelCount"].int_value();
-
-        for (const auto &p : j["parameters"].array_items()) {
-            auto pd = toParameterDescriptor(p);
-            psd.parameters.push_back(pd);
-        }
-
-        for (const auto &p : j["programs"].array_items()) {
-            if (!p.is_string()) {
-                throw Failure("strings expected in programs array");
-            }
-            psd.programs.push_back(p.string_value());
-        }
-
-        psd.inputDomain = toInputDomain(j["inputDomain"].string_value());
-
-        for (const auto &bo : j["basicOutputInfo"].array_items()) {
-            Vamp::HostExt::PluginStaticData::Basic b;
-            toBasicDescriptor(bo, b);
-            psd.basicOutputInfo.push_back(b);
-        }
-
-        return psd;
+        // fallthrough error case
+        return {};
     }
 
     static json11::Json
@@ -619,30 +674,33 @@
     }
 
     static Vamp::HostExt::PluginConfiguration
-    toPluginConfiguration(json11::Json j) {
+    toPluginConfiguration(json11::Json j, std::string &err) {
         
-        std::string err;
         if (!j.has_shape({
                     { "channelCount", json11::Json::NUMBER },
                     { "stepSize", json11::Json::NUMBER },
                     { "blockSize", json11::Json::NUMBER } }, err)) {
-            throw Failure("malformed plugin configuration: " + err);
+            err = "malformed plugin configuration: " + err;
+            return {};
         }
 
         if (!j["parameterValues"].is_null() &&
             !j["parameterValues"].is_object()) {
-            throw Failure("object expected for parameter values");
+            err = "object expected for parameter values";
+            return {};
         }
 
         for (auto &pv : j["parameterValues"].object_items()) {
             if (!pv.second.is_number()) {
-                throw Failure("number expected for parameter value");
+                err = "number expected for parameter value";
+                return {};
             }
         }
     
         if (!j["currentProgram"].is_null() &&
             !j["currentProgram"].is_string()) {
-            throw Failure("string expected for program name");
+            err = "string expected for program name";
+            return {};
         }
 
         Vamp::HostExt::PluginConfiguration config;
@@ -681,30 +739,36 @@
     }
 
     static Vamp::HostExt::PluginLoader::AdapterFlags
-    toAdapterFlags(json11::Json j) {
+    toAdapterFlags(json11::Json j, std::string &err) {
+
+        int flags = 0x0;
 
         if (!j.is_array()) {
-            throw Failure("array expected for adapter flags");
-        }
-        int flags = 0x0;
 
-        for (auto &jj: j.array_items()) {
-            if (!jj.is_string()) {
-                throw Failure("string expected for adapter flag");
-            }
-            std::string text = jj.string_value();
-            if (text == "AdaptInputDomain") {
-                flags |= Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN;
-            } else if (text == "AdaptChannelCount") {
-                flags |= Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT;
-            } else if (text == "AdaptBufferSize") {
-                flags |= Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE;
-            } else if (text == "AdaptAllSafe") {
-                flags |= Vamp::HostExt::PluginLoader::ADAPT_ALL_SAFE;
-            } else if (text == "AdaptAll") {
-                flags |= Vamp::HostExt::PluginLoader::ADAPT_ALL;
-            } else {
-                throw Failure("invalid adapter flag string: " + text);
+            err = "array expected for adapter flags";
+
+        } else {
+
+            for (auto &jj: j.array_items()) {
+                if (!jj.is_string()) {
+                    err = "string expected for adapter flag";
+                    break;
+                }
+                std::string text = jj.string_value();
+                if (text == "AdaptInputDomain") {
+                    flags |= Vamp::HostExt::PluginLoader::ADAPT_INPUT_DOMAIN;
+                } else if (text == "AdaptChannelCount") {
+                    flags |= Vamp::HostExt::PluginLoader::ADAPT_CHANNEL_COUNT;
+                } else if (text == "AdaptBufferSize") {
+                    flags |= Vamp::HostExt::PluginLoader::ADAPT_BUFFER_SIZE;
+                } else if (text == "AdaptAllSafe") {
+                    flags |= Vamp::HostExt::PluginLoader::ADAPT_ALL_SAFE;
+                } else if (text == "AdaptAll") {
+                    flags |= Vamp::HostExt::PluginLoader::ADAPT_ALL;
+                } else {
+                    err = "invalid adapter flag string: " + text;
+                    break;
+                }
             }
         }
 
@@ -722,21 +786,21 @@
     }
 
     static Vamp::HostExt::LoadRequest
-    toLoadRequest(json11::Json j) {
+    toLoadRequest(json11::Json j, std::string &err) {
         
-        std::string err;
-
         if (!j.has_shape({
                     { "pluginKey", json11::Json::STRING },
                     { "inputSampleRate", json11::Json::NUMBER } }, err)) {
-            throw Failure("malformed load request: " + err);
+            err = "malformed load request: " + err;
+            return {};
         }
     
         Vamp::HostExt::LoadRequest req;
         req.pluginKey = j["pluginKey"].string_value();
         req.inputSampleRate = j["inputSampleRate"].number_value();
         if (!j["adapterFlags"].is_null()) {
-            req.adapterFlags = toAdapterFlags(j["adapterFlags"]);
+            req.adapterFlags = toAdapterFlags(j["adapterFlags"], err);
+            if (failed(err)) return {};
         }
         return req;
     }
@@ -755,21 +819,23 @@
 
     static Vamp::HostExt::LoadResponse
     toLoadResponse(json11::Json j,
-                   const PluginHandleMapper &pmapper) {
-
-        std::string err;
+                   const PluginHandleMapper &pmapper, std::string &err) {
 
         if (!j.has_shape({
                     { "pluginHandle", json11::Json::NUMBER },
                     { "staticData", json11::Json::OBJECT },
                     { "defaultConfiguration", json11::Json::OBJECT } }, err)) {
-            throw Failure("malformed load response: " + err);
+            err = "malformed load response: " + err;
+            return {};
         }
 
         Vamp::HostExt::LoadResponse resp;
         resp.plugin = pmapper.handleToPlugin(j["pluginHandle"].int_value());
-        resp.staticData = toPluginStaticData(j["staticData"]);
-        resp.defaultConfiguration = toPluginConfiguration(j["defaultConfiguration"]);
+        resp.staticData = toPluginStaticData(j["staticData"], err);
+        if (failed(err)) return {};
+        resp.defaultConfiguration = toPluginConfiguration(j["defaultConfiguration"],
+                                                          err);
+        if (failed(err)) return {};
         return resp;
     }
 
@@ -787,19 +853,19 @@
 
     static Vamp::HostExt::ConfigurationRequest
     toConfigurationRequest(json11::Json j,
-                           const PluginHandleMapper &pmapper) {
-
-        std::string err;
+                           const PluginHandleMapper &pmapper, std::string &err) {
 
         if (!j.has_shape({
                     { "pluginHandle", json11::Json::NUMBER },
                     { "configuration", json11::Json::OBJECT } }, err)) {
-            throw Failure("malformed configuration request: " + err);
+            err = "malformed configuration request: " + err;
+            return {};
         }
 
         Vamp::HostExt::ConfigurationRequest cr;
         cr.plugin = pmapper.handleToPlugin(j["pluginHandle"].int_value());
-        cr.configuration = toPluginConfiguration(j["configuration"]);
+        cr.configuration = toPluginConfiguration(j["configuration"], err);
+        if (failed(err)) return {};
         return cr;
     }
 
@@ -822,18 +888,20 @@
 
     static Vamp::HostExt::ConfigurationResponse
     toConfigurationResponse(json11::Json j,
-                            const PluginHandleMapper &pmapper) {
+                            const PluginHandleMapper &pmapper, std::string &err) {
         
         Vamp::HostExt::ConfigurationResponse cr;
 
         cr.plugin = pmapper.handleToPlugin(j["pluginHandle"].int_value());
         
         if (!j["outputList"].is_array()) {
-            throw Failure("array expected for output list");
+            err = "array expected for output list";
+            return {};
         }
 
         for (const auto &o: j["outputList"].array_items()) {
-            cr.outputs.push_back(toOutputDescriptor(o));
+            cr.outputs.push_back(toOutputDescriptor(o, err));
+            if (failed(err)) return {};
         }
 
         return cr;
@@ -871,14 +939,13 @@
     static Vamp::HostExt::ProcessRequest
     toProcessRequest(json11::Json j,
                      const PluginHandleMapper &pmapper,
-                     BufferSerialisation &serialisation) {
-
-        std::string err;
+                     BufferSerialisation &serialisation, std::string &err) {
 
         if (!j.has_shape({
                     { "pluginHandle", json11::Json::NUMBER },
                     { "processInput", json11::Json::OBJECT } }, err)) {
-            throw Failure("malformed process request: " + err);
+            err = "malformed process request: " + err;
+            return {};
         }
 
         auto input = j["processInput"];
@@ -886,18 +953,22 @@
         if (!input.has_shape({
                     { "timestamp", json11::Json::OBJECT },
                     { "inputBuffers", json11::Json::ARRAY } }, err)) {
-            throw Failure("malformed process request: " + err);
+            err = "malformed process request: " + err;
+            return {};
         }
 
         Vamp::HostExt::ProcessRequest r;
         r.plugin = pmapper.handleToPlugin(j["pluginHandle"].int_value());
 
-        r.timestamp = toRealTime(input["timestamp"]);
+        r.timestamp = toRealTime(input["timestamp"], err);
+        if (failed(err)) return {};
 
         for (auto a: input["inputBuffers"].array_items()) {
 
             if (a["b64values"].is_string()) {
-                std::vector<float> buf = toFloatBuffer(a["b64values"].string_value());
+                std::vector<float> buf = toFloatBuffer(a["b64values"].string_value(),
+                                                       err);
+                if (failed(err)) return {};
                 r.inputBuffers.push_back(buf);
                 serialisation = BufferSerialisation::Base64;
 
@@ -910,7 +981,8 @@
                 serialisation = BufferSerialisation::Text;
 
             } else {
-                throw Failure("expected values or b64values in inputBuffers object");
+                err = "expected values or b64values in inputBuffers object";
+                return {};
             }
         }
 
@@ -1063,39 +1135,37 @@
         jo["errorText"] = std::string("error in ") + type + " request: " + errorText;
         return json11::Json(jo);
     }
-
-    static json11::Json
-    fromException(const std::exception &e, RRType responseType) {
-
-        return fromError(e.what(), responseType);
-    }
     
 private: // go private briefly for a couple of helper functions
     
     static void
-    checkTypeField(json11::Json j, std::string expected) {
+    checkTypeField(json11::Json j, std::string expected, std::string &err) {
         if (!j["type"].is_string()) {
-            throw Failure("string expected for type");
+            err = "string expected for type";
+            return;
         }
         if (j["type"].string_value() != expected) {
-            throw Failure("expected value \"" + expected + "\" for type");
+            err = "expected value \"" + expected + "\" for type";
+            return;
         }
     }
 
     static bool
-    successful(json11::Json j) {
+    successful(json11::Json j, std::string &err) {
         if (!j["success"].is_bool()) {
-            throw Failure("bool expected for success");
+            err = "bool expected for success";
+            return false;
         }
         return j["success"].bool_value();
     }
 
 public:
     static RRType
-    getRequestResponseType(json11::Json j) {
+    getRequestResponseType(json11::Json j, std::string &err) {
 
         if (!j["type"].is_string()) {
-            throw Failure("string expected for type");
+            err = "string expected for type";
+            return RRType::NotValid;
         }
         
         std::string type = j["type"].string_value();
@@ -1105,93 +1175,106 @@
 	else if (type == "configure") return RRType::Configure;
 	else if (type == "process") return RRType::Process;
 	else if (type == "finish") return RRType::Finish;
+        else if (type == "invalid") return RRType::NotValid;
 	else {
-	    throw Failure("unknown or unexpected request/response type \"" +
-                          type + "\"");
+	    err = "unknown or unexpected request/response type \"" + type + "\"";
+            return RRType::NotValid;
 	}
     }
 
     static void
-    toVampRequest_List(json11::Json j) {
-        
-        checkTypeField(j, "list");
+    toVampRequest_List(json11::Json j, std::string &err) {
+        checkTypeField(j, "list", err);
     }
 
     static Vamp::HostExt::ListResponse
-    toVampResponse_List(json11::Json j) {
+    toVampResponse_List(json11::Json j, std::string &err) {
 
         Vamp::HostExt::ListResponse resp;
-        if (successful(j)) {
+        if (successful(j, err) && !failed(err)) {
             for (const auto &a: j["content"]["plugins"].array_items()) {
-                resp.pluginData.push_back(toPluginStaticData(a));
+                resp.pluginData.push_back(toPluginStaticData(a, err));
+                if (failed(err)) return {};
             }
         }
+        
         return resp;
     }
 
     static Vamp::HostExt::LoadRequest
-    toVampRequest_Load(json11::Json j) {
+    toVampRequest_Load(json11::Json j, std::string &err) {
         
-        checkTypeField(j, "load");
-        return toLoadRequest(j["content"]);
+        checkTypeField(j, "load", err);
+        if (failed(err)) return {};
+        return toLoadRequest(j["content"], err);
     }
     
     static Vamp::HostExt::LoadResponse
-    toVampResponse_Load(json11::Json j, const PluginHandleMapper &pmapper) {
+    toVampResponse_Load(json11::Json j,
+                        const PluginHandleMapper &pmapper,
+                        std::string &err) {
         
         Vamp::HostExt::LoadResponse resp;
-        if (successful(j)) {
-            resp = toLoadResponse(j["content"], pmapper);
+        if (successful(j, err) && !failed(err)) {
+            resp = toLoadResponse(j["content"], pmapper, err);
         }
         return resp;
     }
     
     static Vamp::HostExt::ConfigurationRequest
-    toVampRequest_Configure(json11::Json j, const PluginHandleMapper &pmapper) {
+    toVampRequest_Configure(json11::Json j,
+                            const PluginHandleMapper &pmapper,
+                            std::string &err) {
         
-        checkTypeField(j, "configure");
-        return toConfigurationRequest(j["content"], pmapper);
+        checkTypeField(j, "configure", err);
+        if (failed(err)) return {};
+        return toConfigurationRequest(j["content"], pmapper, err);
     }
     
     static Vamp::HostExt::ConfigurationResponse
-    toVampResponse_Configure(json11::Json j, const PluginHandleMapper &pmapper) {
+    toVampResponse_Configure(json11::Json j,
+                             const PluginHandleMapper &pmapper,
+                             std::string &err) {
         
         Vamp::HostExt::ConfigurationResponse resp;
-        if (successful(j)) {
-            resp = toConfigurationResponse(j["content"], pmapper);
+        if (successful(j, err) && !failed(err)) {
+            resp = toConfigurationResponse(j["content"], pmapper, err);
         }
         return resp;
     }
     
     static Vamp::HostExt::ProcessRequest
     toVampRequest_Process(json11::Json j, const PluginHandleMapper &pmapper,
-                          BufferSerialisation &serialisation) {
+                          BufferSerialisation &serialisation, std::string &err) {
         
-        checkTypeField(j, "process");
-        return toProcessRequest(j["content"], pmapper, serialisation);
+        checkTypeField(j, "process", err);
+        if (failed(err)) return {};
+        return toProcessRequest(j["content"], pmapper, serialisation, err);
     }
     
     static Vamp::HostExt::ProcessResponse
     toVampResponse_Process(json11::Json j,
                            const PluginHandleMapper &pmapper,
-                           BufferSerialisation &serialisation) {
+                           BufferSerialisation &serialisation, std::string &err) {
         
         Vamp::HostExt::ProcessResponse resp;
-        if (successful(j)) {
+        if (successful(j, err) && !failed(err)) {
             auto jc = j["content"];
             auto h = jc["pluginHandle"].int_value();
             resp.plugin = pmapper.handleToPlugin(h);
             resp.features = toFeatureSet(jc["features"],
                                          *pmapper.handleToOutputIdMapper(h),
-                                         serialisation);
+                                         serialisation, err);
         }
         return resp;
     }
     
     static Vamp::HostExt::FinishRequest
-    toVampRequest_Finish(json11::Json j, const PluginHandleMapper &pmapper) {
+    toVampRequest_Finish(json11::Json j, const PluginHandleMapper &pmapper,
+                         std::string &err) {
         
-        checkTypeField(j, "finish");
+        checkTypeField(j, "finish", err);
+        if (failed(err)) return {};
         Vamp::HostExt::FinishRequest req;
         req.plugin = pmapper.handleToPlugin
             (j["content"]["pluginHandle"].int_value());
@@ -1201,16 +1284,16 @@
     static Vamp::HostExt::ProcessResponse
     toVampResponse_Finish(json11::Json j,
                           const PluginHandleMapper &pmapper,
-                          BufferSerialisation &serialisation) {
+                          BufferSerialisation &serialisation, std::string &err) {
         
         Vamp::HostExt::ProcessResponse resp;
-        if (successful(j)) {
+        if (successful(j, err) && !failed(err)) {
             auto jc = j["content"];
             auto h = jc["pluginHandle"].int_value();
             resp.plugin = pmapper.handleToPlugin(h);
             resp.features = toFeatureSet(jc["features"],
                                          *pmapper.handleToOutputIdMapper(h),
-                                         serialisation);
+                                         serialisation, err);
         }
         return resp;
     }
--- a/json/json-test.cpp	Mon Sep 19 15:52:38 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-
-#include "VampJson.h"
-
-using std::cerr;
-using std::endl;
-
-int main(int, char **)
-{
-    Vamp::PluginBase::ParameterDescriptor d;
-    d.identifier = "threshold";
-    d.name = "Energy rise threshold";
-    d.description = "Energy rise within a frequency bin necessary to count toward broadband total";
-    d.unit = "dB";
-    d.minValue = 0;
-    d.maxValue = 20.5;
-    d.defaultValue = 3;
-    d.isQuantized = false;
-    cerr << VampJson::fromParameterDescriptor(d).dump() << endl;
-
-    Vamp::Plugin::OutputDescriptor od;
-    od.identifier = "powerspectrum";
-    od.name = "Power Spectrum";
-    od.description = "Power values of the frequency spectrum bins calculated from the input signal";
-    od.unit = "";
-    od.hasFixedBinCount = true;
-    od.binCount = 513;
-    od.hasKnownExtents = false;
-    od.isQuantized = false;
-    od.sampleType = Vamp::Plugin::OutputDescriptor::OneSamplePerStep;
-    cerr << VampJson::fromOutputDescriptor(od).dump() << endl;
-
-    cerr << VampJson::fromFeature(Vamp::Plugin::Feature()).dump() << endl;
-    
-    Vamp::Plugin::Feature f;
-    f.hasTimestamp = true;
-    f.timestamp = Vamp::RealTime::fromSeconds(3.14159);
-    f.hasDuration = false;
-    f.values = { 1, 2, 3.000001, 4, 5, 6, 6.5, 7 };
-    f.label = "A feature";
-    cerr << VampJson::fromFeature(f).dump() << endl;
-
-    Vamp::Plugin::FeatureSet fs;
-    fs[0].push_back(f);
-    std::string fs_str = VampJson::fromFeatureSet(fs).dump();
-    cerr << fs_str << endl;
-
-    std::string err;
-    
-    try {
-	Vamp::Plugin::ParameterDescriptor d1 =
-	    VampJson::toParameterDescriptor
-	    (json11::Json::parse
-	     (VampJson::fromParameterDescriptor(d).dump(), err));
-	if (err != "") {
-	    cerr << "error returned from parser: " << err << endl;
-	}
-	cerr << "\nsuccessfully converted parameter descriptor back from json: serialising it again, we get:" << endl;
-	cerr << VampJson::fromParameterDescriptor(d1).dump() << endl;
-    } catch (std::runtime_error &e) {
-	cerr << "caught exception: " << e.what() << endl;
-    }
-    
-    try {
-	Vamp::Plugin::OutputDescriptor od1 =
-	    VampJson::toOutputDescriptor
-	    (json11::Json::parse
-	     (VampJson::fromOutputDescriptor(od).dump(), err));
-	if (err != "") {
-	    cerr << "error returned from parser: " << err << endl;
-	}
-	cerr << "\nsuccessfully converted output descriptor back from json: serialising it again, we get:" << endl;
-	cerr << VampJson::fromOutputDescriptor(od1).dump() << endl;
-    } catch (std::runtime_error &e) {
-	cerr << "caught exception: " << e.what() << endl;
-    }
-    
-    try {
-	Vamp::Plugin::FeatureSet fs1 =
-	    VampJson::toFeatureSet
-	    (json11::Json::parse(fs_str, err));
-	if (err != "") {
-	    cerr << "error returned from parser: " << err << endl;
-	}
-	cerr << "\nsuccessfully converted feature set back from json: serialising it again, we get:" << endl;
-	cerr << VampJson::fromFeatureSet(fs1).dump() << endl;
-    } catch (std::runtime_error &e) {
-	cerr << "caught exception: " << e.what() << endl;
-    }
-
-    return 0;
-}
-
-    
-    
--- a/utilities/json-cli.cpp	Mon Sep 19 15:52:38 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-
-//!!! This program was an early test -- it should still compile but
-//!!! it's incomplete. Remove it and use the server program instead.
-
-#include "VampJson.h"
-#include "bits/CountingPluginHandleMapper.h"
-
-#include <iostream>
-#include <sstream>
-#include <stdexcept>
-
-#include <map>
-#include <set>
-
-using namespace std;
-using namespace Vamp;
-using namespace Vamp::HostExt;
-using namespace json11;
-using namespace vampipe;
-
-static CountingPluginHandleMapper mapper;
-
-Vamp::HostExt::LoadResponse
-loadPlugin(json11::Json j) {
-
-    auto req = VampJson::toLoadRequest(j);
-    auto loader = Vamp::HostExt::PluginLoader::getInstance();
-    auto response = loader->loadPlugin(req);
-    
-    if (!response.plugin) {
-	throw VampJson::Failure("plugin load failed");
-    }
-    
-    return response;
-}
-    
-Vamp::HostExt::ConfigurationResponse
-configurePlugin(Vamp::Plugin *plugin, json11::Json j) {
-    
-    auto config = VampJson::toPluginConfiguration(j);
-    Vamp::HostExt::ConfigurationRequest req;
-    req.plugin = plugin;
-    req.configuration = config;
-    auto loader = Vamp::HostExt::PluginLoader::getInstance();
-
-    auto response = loader->configurePlugin(req);
-    if (response.outputs.empty()) {
-	throw VampJson::Failure("plugin initialisation failed (invalid channelCount, stepSize, blockSize?)");
-    }
-    return response;
-}
-
-Json
-handle_list(Json content)
-{
-    if (content != Json()) {
-	throw VampJson::Failure("no content expected for list request");
-    }
-    
-    auto loader = PluginLoader::getInstance();
-    auto resp = loader->listPluginData();
-
-    Json::array j;
-    for (const auto &pd: resp.pluginData) {
-	j.push_back(VampJson::fromPluginStaticData(pd));
-    }
-    return Json(j);
-}
-
-Json
-handle_load(Json j)
-{
-    auto loadResponse = loadPlugin(j);
-
-    if (!loadResponse.plugin) {
-	throw VampJson::Failure("plugin load failed");
-    }
-
-    mapper.addPlugin(loadResponse.plugin);
-    
-    return VampJson::fromLoadResponse(loadResponse, mapper);
-}
-
-Json
-handle_configure(Json j)
-{
-    string err;
-
-    if (!j.has_shape({
-		{ "pluginHandle", Json::NUMBER },
-		{ "configuration", Json::OBJECT }}, err)) {
-	throw VampJson::Failure("malformed configuration request: " + err);
-    }
-
-    int32_t handle = j["pluginHandle"].int_value();
-
-    if (mapper.isConfigured(handle)) {
-	throw VampJson::Failure("plugin has already been configured");
-    }
-
-    Plugin *plugin = mapper.handleToPlugin(handle);
-    
-    Json config = j["configuration"];
-
-    auto response = configurePlugin(plugin, config);
-
-    mapper.markConfigured(handle, 0, 0); //!!!
-
-    cerr << "Configured and initialised plugin " << handle << endl;
-
-    return VampJson::fromConfigurationResponse(response, mapper);
-}
-
-Json
-handle(string input)
-{
-    string err;
-    Json j = Json::parse(input, err);
-
-    if (err != "") {
-	throw VampJson::Failure("invalid request: " + err);
-    }
-
-    if (!j["type"].is_string()) {
-	throw VampJson::Failure("type expected in request");
-    }
-
-    if (!j["content"].is_null() &&
-	!j["content"].is_object()) {
-	throw VampJson::Failure("object expected for content");
-    }
-
-    string verb = j["type"].string_value();
-    Json content = j["content"];
-    Json result;
-
-    if (verb == "list") {
-	result = handle_list(content);
-    } else if (verb == "load") {
-	result = handle_load(content);
-    } else if (verb == "configure") {
-	result = handle_configure(content);
-    } else {
-	throw VampJson::Failure("unknown verb: " + verb +
-				" (known verbs are: list load configure)");
-    }
-
-    return result;
-}
-
-Json
-success_response(Json payload)
-{
-    Json::object obj;
-    obj["success"] = true;
-    obj["response"] = payload;
-    return Json(obj);
-}
-
-Json
-error_response(string text)
-{
-    Json::object obj;
-    obj["success"] = false;
-    obj["errorText"] = text;
-    return Json(obj);
-}
-
-template<typename T>
-T &getline(T &in, string prompt, string &out)
-{
-    cerr << prompt;
-    return getline(in, out);
-}
-
-int main(int, char **)
-{
-    string line;
-
-    while (getline(cin, "> ", line)) {
-	try {
-	    Json result = handle(line);
-	    cout << success_response(result).dump() << endl;
-	} catch (const VampJson::Failure &e) {
-	    cout << error_response(e.what()).dump() << endl;
-	} catch (const PluginHandleMapper::NotFound &e) {
-	    cout << error_response(e.what()).dump() << endl;
-	}
-    }
-
-    return 0;
-}
-
-
--- a/utilities/json-to-capnp.cpp	Mon Sep 19 15:52:38 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,139 +0,0 @@
-
-#include "VampJson.h"
-#include "VampnProto.h"
-
-#include <iostream>
-#include <sstream>
-#include <stdexcept>
-
-#include "bits/PreservingPluginHandleMapper.h"
-
-using namespace std;
-using namespace json11;
-using namespace vampipe;
-
-// Accepting JSON objects with two fields, "type" and "payload". The
-// "type" string corresponds to the JSON schema filename
-// (e.g. "outputdescriptor") and the "payload" is the JSON object
-// encoded with that schema.
-
-Json
-json_input(string input)
-{
-    string err;
-    Json j = Json::parse(input, err);
-    if (err != "") {
-	throw VampJson::Failure("invalid json: " + err);
-    }
-    if (!j.is_object()) {
-	throw VampJson::Failure("object expected at top level");
-    }
-    if (!j["type"].is_string()) {
-	throw VampJson::Failure("string expected for type field");
-    }
-    if (!j["payload"].is_object()) {
-	throw VampJson::Failure("object expected for payload field");
-    }
-    return j;
-}
-
-void 
-handle_input(::capnp::MallocMessageBuilder &message, string input)
-{
-    string err;
-
-    Json j = json_input(input);
-    string type = j["type"].string_value();
-    Json payload = j["payload"];
-    VampJson::BufferSerialisation serialisation;
-
-    if (type == "configurationrequest") {
-	auto req = message.initRoot<ConfigurationRequest>();
-	PreservingPluginHandleMapper mapper;
-	VampnProto::buildConfigurationRequest
-	    (req, VampJson::toConfigurationRequest(payload, mapper), mapper);
-
-    } else if (type == "configurationresponse") {
-	auto resp = message.initRoot<ConfigurationResponse>();
-	PreservingPluginHandleMapper mapper;
-	VampnProto::buildConfigurationResponse
-	    (resp, VampJson::toConfigurationResponse(payload, mapper), mapper);
-
-    } else if (type == "feature") {
-	auto f = message.initRoot<Feature>();
-	VampnProto::buildFeature
-	    (f, VampJson::toFeature(payload, serialisation));
-
-    } else if (type == "featureset") {
-	auto fs = message.initRoot<FeatureSet>();
-	PreservingPluginOutputIdMapper omapper;
-	VampnProto::buildFeatureSet
-	    (fs, VampJson::toFeatureSet(payload, omapper, serialisation), omapper);
-
-    } else if (type == "loadrequest") {
-	auto req = message.initRoot<LoadRequest>();
-	VampnProto::buildLoadRequest
-	    (req, VampJson::toLoadRequest(payload));
-	
-    } else if (type == "loadresponse") {
-	auto resp = message.initRoot<LoadResponse>();
-	PreservingPluginHandleMapper mapper;
-	VampnProto::buildLoadResponse
-	    (resp, VampJson::toLoadResponse(payload, mapper), mapper);
-
-    } else if (type == "outputdescriptor") {
-	auto od = message.initRoot<OutputDescriptor>();
-	VampnProto::buildOutputDescriptor
-	    (od, VampJson::toOutputDescriptor(payload));
-
-    } else if (type == "parameterdescriptor") {
-	auto pd = message.initRoot<ParameterDescriptor>();
-	VampnProto::buildParameterDescriptor
-	    (pd, VampJson::toParameterDescriptor(payload));
-
-    } else if (type == "pluginconfiguration") {
-	auto pc = message.initRoot<PluginConfiguration>();
-	auto config = VampJson::toPluginConfiguration(payload);
-	VampnProto::buildPluginConfiguration(pc, config);
-
-    } else if (type == "pluginstaticdata") {
-	auto pc = message.initRoot<PluginStaticData>();
-	auto sd = VampJson::toPluginStaticData(payload);
- 	VampnProto::buildPluginStaticData(pc, sd);
-
-    } else if (type == "processrequest") {
-	auto p = message.initRoot<ProcessRequest>();
-	PreservingPluginHandleMapper mapper;
-	VampnProto::buildProcessRequest
-	    (p, VampJson::toProcessRequest(payload, mapper, serialisation), mapper);
-
-    } else if (type == "realtime") {
-	auto b = message.initRoot<RealTime>();
-	VampnProto::buildRealTime
-	    (b, VampJson::toRealTime(payload));
-	
-    } else {
-	throw VampJson::Failure("unknown or unsupported JSON schema type " +
-				type);
-    }
-}
-    
-int main(int, char **)
-{
-    string input;
-
-    while (getline(cin, input)) {
-	try {
-	    ::capnp::MallocMessageBuilder message;
-	    handle_input(message, input);
-	    writePackedMessageToFd(1, message); // stdout
-	    return 0;
-	} catch (const VampJson::Failure &e) {
-	    cerr << "Failed to convert JSON to Cap'n Proto message: "
-		 << e.what() << endl;
-	    return 1;
-	}
-    }
-}
-
-
--- a/utilities/vampipe-convert.cpp	Mon Sep 19 15:52:38 2016 +0100
+++ b/utilities/vampipe-convert.cpp	Tue Sep 20 16:35:47 2016 +0100
@@ -37,41 +37,37 @@
 }
 
 Json
-convertRequestJson(string input)
+convertRequestJson(string input, string &err)
 {
-    string err;
     Json j = Json::parse(input, err);
     if (err != "") {
-	throw VampJson::Failure("invalid json: " + err);
+	err = "invalid json: " + err;
+	return {};
     }
     if (!j.is_object()) {
-	throw VampJson::Failure("object expected at top level");
-    }
-    if (!j["type"].is_string()) {
-	throw VampJson::Failure("string expected for type field");
-    }
-    if (!j["content"].is_null() && !j["content"].is_object()) {
-	throw VampJson::Failure("object expected for content field");
+	err = "object expected at top level";
+    } else if (!j["type"].is_string()) {
+	err = "string expected for type field";
+    } else if (!j["content"].is_null() && !j["content"].is_object()) {
+	err = "object expected for content field";
     }
     return j;
 }
 
 Json
-convertResponseJson(string input)
+convertResponseJson(string input, string &err)
 {
-    string err;
     Json j = Json::parse(input, err);
     if (err != "") {
-	throw VampJson::Failure("invalid json: " + err);
+	err = "invalid json: " + err;
+	return {};
     }
     if (!j.is_object()) {
-	throw VampJson::Failure("object expected at top level");
-    }
-    if (!j["success"].is_bool()) {
-	throw VampJson::Failure("bool expected for success field");
-    }
-    if (!j["content"].is_object()) {
-	throw VampJson::Failure("object expected for content field");
+	err = "object expected at top level";
+    } else if (!j["success"].is_bool()) {
+	err = "bool expected for success field";
+    } else if (!j["content"].is_object()) {
+	err = "object expected for content field";
     }
     return j;
 }
@@ -82,38 +78,42 @@
 PreservingPluginHandleMapper mapper;
 
 RequestOrResponse
-readRequestJson()
+readRequestJson(string &err)
 {
     RequestOrResponse rr;
     rr.direction = RequestOrResponse::Request;
 
     string input;
     if (!getline(cin, input)) {
+	// the EOF case, not actually an error
 	rr.type = RRType::NotValid;
 	return rr;
     }
     
-    Json j = convertRequestJson(input);
+    Json j = convertRequestJson(input, err);
+    if (err != "") return {};
 
-    rr.type = VampJson::getRequestResponseType(j);
+    rr.type = VampJson::getRequestResponseType(j, err);
+    if (err != "") return {};
+    
     VampJson::BufferSerialisation serialisation = VampJson::BufferSerialisation::Text;
 
     switch (rr.type) {
 
     case RRType::List:
-	VampJson::toVampRequest_List(j); // type check only
+	VampJson::toVampRequest_List(j, err); // type check only
 	break;
     case RRType::Load:
-	rr.loadRequest = VampJson::toVampRequest_Load(j);
+	rr.loadRequest = VampJson::toVampRequest_Load(j, err);
 	break;
     case RRType::Configure:
-	rr.configurationRequest = VampJson::toVampRequest_Configure(j, mapper);
+	rr.configurationRequest = VampJson::toVampRequest_Configure(j, mapper, err);
 	break;
     case RRType::Process:
-	rr.processRequest = VampJson::toVampRequest_Process(j, mapper, serialisation);
+	rr.processRequest = VampJson::toVampRequest_Process(j, mapper, serialisation, err);
 	break;
     case RRType::Finish:
-	rr.finishRequest = VampJson::toVampRequest_Finish(j, mapper);
+	rr.finishRequest = VampJson::toVampRequest_Finish(j, mapper, err);
 	break;
     case RRType::NotValid:
 	break;
@@ -158,20 +158,24 @@
 }
 
 RequestOrResponse
-readResponseJson()
+readResponseJson(string &err)
 {
     RequestOrResponse rr;
     rr.direction = RequestOrResponse::Response;
 
     string input;
     if (!getline(cin, input)) {
+	// the EOF case, not actually an error
 	rr.type = RRType::NotValid;
 	return rr;
     }
 
-    Json j = convertResponseJson(input);
+    Json j = convertResponseJson(input, err);
+    if (err != "") return {};
 
-    rr.type = VampJson::getRequestResponseType(j);
+    rr.type = VampJson::getRequestResponseType(j, err);
+    if (err != "") return {};
+    
     VampJson::BufferSerialisation serialisation = VampJson::BufferSerialisation::Text;
 
     rr.success = j["success"].bool_value();
@@ -180,19 +184,19 @@
     switch (rr.type) {
 
     case RRType::List:
-	rr.listResponse = VampJson::toVampResponse_List(j);
+	rr.listResponse = VampJson::toVampResponse_List(j, err);
 	break;
     case RRType::Load:
-	rr.loadResponse = VampJson::toVampResponse_Load(j, mapper);
+	rr.loadResponse = VampJson::toVampResponse_Load(j, mapper, err);
 	break;
     case RRType::Configure:
-	rr.configurationResponse = VampJson::toVampResponse_Configure(j, mapper);
+	rr.configurationResponse = VampJson::toVampResponse_Configure(j, mapper, err);
 	break;
     case RRType::Process: 
-	rr.processResponse = VampJson::toVampResponse_Process(j, mapper, serialisation);
+	rr.processResponse = VampJson::toVampResponse_Process(j, mapper, serialisation, err);
 	break;
     case RRType::Finish:
-	rr.finishResponse = VampJson::toVampResponse_Finish(j, mapper, serialisation);
+	rr.finishResponse = VampJson::toVampResponse_Finish(j, mapper, serialisation, err);
 	break;
     case RRType::NotValid:
 	break;
@@ -388,12 +392,12 @@
 }
 
 RequestOrResponse
-readInputJson(RequestOrResponse::Direction direction)
+readInputJson(RequestOrResponse::Direction direction, string &err)
 {
     if (direction == RequestOrResponse::Request) {
-	return readRequestJson();
+	return readRequestJson(err);
     } else {
-	return readResponseJson();
+	return readResponseJson(err);
     }
 }
 
@@ -418,7 +422,10 @@
 readInput(string format, RequestOrResponse::Direction direction)
 {
     if (format == "json") {
-	return readInputJson(direction);
+	string err;
+	auto result = readInputJson(direction, err);
+	if (err != "") throw runtime_error(err);
+	else return result;
     } else if (format == "capnp") {
 	return readInputCapnp(direction);
     } else {