Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F3768060
D3424.id8841.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
23 KB
Referenced Files
None
Subscribers
None
D3424.id8841.diff
View Options
diff --git a/Cargo.toml b/Cargo.toml
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,10 +1,12 @@
[workspace]
members = [
+ "ds-http-client",
"fantoir-datasource",
"language-subtag-registry-datasource",
"rfc-datasource",
"opendatasoft-explore-api",
+ "sparql-client",
]
resolver = "2"
diff --git a/ds-http-client/Cargo.toml b/ds-http-client/Cargo.toml
new file mode 100644
--- /dev/null
+++ b/ds-http-client/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "ds-http-client"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+lazy_static = "~1.5.0"
+
+[dependencies.reqwest]
+version = "~0.12.7"
+features = ["gzip", "deflate"]
+
+[dependencies.tokio]
+version = "~1.39.3"
+features = ["full"]
diff --git a/fantoir-datasource/src/services/http_client.rs b/ds-http-client/src/lib.rs
copy from fantoir-datasource/src/services/http_client.rs
copy to ds-http-client/src/lib.rs
--- a/fantoir-datasource/src/services/http_client.rs
+++ b/ds-http-client/src/lib.rs
@@ -1,13 +1,20 @@
+//! # HTTP client
+//!
+//! High-level interface to Hyper/reqwest HTTP client.
+//!
+//! This library is optimized to work with Nasqueron Datasources components.
+
+use std::collections::HashMap;
use std::io::Error as IOError;
use std::path::Path;
use lazy_static::lazy_static;
-use reqwest::Client as ReqwestClient;
+use reqwest::{Client as ReqwestClient, RequestBuilder};
use reqwest::ClientBuilder;
use reqwest::Error as ReqwestError;
use reqwest::IntoUrl;
use reqwest::Response;
-use reqwest::header::HeaderMap;
+use reqwest::header::{HeaderMap, HeaderValue};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
@@ -19,11 +26,12 @@
lazy_static! {
pub static ref USER_AGENT: String = format!(
- "{}/{} (https://databases.nasqueron.org/)",
+ "{}/{}",
env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")
);
}
+/// Gets the default user agent
pub fn get_user_agent () -> &'static str {
&USER_AGENT
}
@@ -32,18 +40,15 @@
HTTP client
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/// HTTP client
pub struct Client {
client: ReqwestClient,
}
impl Client {
pub fn new(headers: Option<HeaderMap>) -> Self {
- let headers = headers
- .unwrap_or(HeaderMap::new());
-
let client = ClientBuilder::new()
- .user_agent(get_user_agent())
- .default_headers(headers)
+ .default_headers(build_default_headers(headers))
.gzip(true)
.deflate(true)
.build()
@@ -55,16 +60,31 @@
}
pub async fn get<T>(&self, url: T) -> Result<Response, Error>
- where T: IntoUrl {
- self.client
+ where T: IntoUrl {
+ let request = self.client.get(url);
+ self.run(request).await
+ }
+
+ pub async fn get_with_headers<T>(&self, url: T, headers: HashMap<String, String>) -> Result<Response, Error>
+ where T: IntoUrl {
+ let headers = parse_headers(headers);
+
+ let request = self.client
.get(url)
+ .headers(headers);
+
+ self.run(request).await
+ }
+
+ pub async fn run(&self, request: RequestBuilder) -> Result<Response, Error> {
+ request
.send()
.await
.map_err(|error| Error::Reqwest(error))
}
pub async fn download<P, T>(&self, url: T, target_path: P) -> Result<usize, Error>
- where T: IntoUrl, P: AsRef<Path> {
+ where T: IntoUrl, P: AsRef<Path> {
let mut file = File::create(target_path)
.await
.map_err(|error| Error::IO(error))?;
@@ -76,15 +96,41 @@
.await
.map_err(|error| Error::Reqwest(error))?
{
- bytes_read += file.write(chunk.as_ref())
- .await
- .map_err(|error| Error::IO(error))?;
+ bytes_read += file.write(chunk.as_ref())
+ .await
+ .map_err(|error| Error::IO(error))?;
}
Ok(bytes_read)
}
}
+/* -------------------------------------------------------------
+ HTTP client utilities
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+pub fn parse_headers(headers: HashMap<String, String>) -> HeaderMap {
+ headers
+ .iter()
+ .map(|(name, value)| (
+ name.parse().expect("Can't parse header name"),
+ value.parse().expect("Can't parse header value")
+ ))
+ .collect()
+}
+
+fn build_default_headers(headers: Option<HeaderMap>) -> HeaderMap {
+ let mut headers = headers
+ .unwrap_or(HeaderMap::new());
+
+ // RFC 7231 states User-Agent header SHOULD be sent.
+ if !headers.contains_key("User-Agent") {
+ headers.append("User-Agent", HeaderValue::from_static(get_user_agent()));
+ }
+
+ headers
+}
+
/* -------------------------------------------------------------
HTTP client error
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
diff --git a/fantoir-datasource/Cargo.toml b/fantoir-datasource/Cargo.toml
--- a/fantoir-datasource/Cargo.toml
+++ b/fantoir-datasource/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "fantoir-datasource"
-version = "0.1.1"
+version = "0.2.0"
edition = "2021"
description = "Generates a Postgres table from FANTOIR raw file"
authors = [
@@ -10,11 +10,12 @@
[dependencies]
chrono = "~0.4.38"
+ds-http-client = { version = "0.1.0", path = "../ds-http-client" }
lazy_static = "~1.5.0"
opendatasoft-explore-api = { version = "0.1.1", path = "../opendatasoft-explore-api" }
oxrdf = "~0.1.7"
regex = "~1.10.6"
-sparesults = "~0.1.8"
+sparql-client = { version = "0.1.0", path = "../sparql-client" }
[dependencies.async-scoped]
version = "~0.9.0"
@@ -24,10 +25,6 @@
version = "~4.5.16"
features = ["derive"]
-[dependencies.reqwest]
-version = "~0.11.18"
-features = ["gzip", "deflate"]
-
[dependencies.sqlx]
version = "~0.8.1"
features = ["runtime-tokio-native-tls", "postgres", "chrono"]
diff --git a/fantoir-datasource/src/commands/fetch/mod.rs b/fantoir-datasource/src/commands/fetch/mod.rs
--- a/fantoir-datasource/src/commands/fetch/mod.rs
+++ b/fantoir-datasource/src/commands/fetch/mod.rs
@@ -13,7 +13,7 @@
use crate::commands::fetch::fantoir_file::FantoirFile;
use crate::commands::fetch::os::is_command_available;
-use crate::services::http_client::Client as HttpClient;
+use crate::services::http_client::build_http_client;
mod fantoir_file;
mod os;
@@ -36,7 +36,7 @@
}
let target_path = get_fantoir_zip_path();
- if let Err(error) = HttpClient::new(None).download(&fantoir_file.url, &target_path).await {
+ if let Err(error) = build_http_client().download(&fantoir_file.url, &target_path).await {
eprintln!("Can't download FANTOIR file: {:?}", error);
exit(16);
}
diff --git a/fantoir-datasource/src/commands/wikidata/mod.rs b/fantoir-datasource/src/commands/wikidata/mod.rs
--- a/fantoir-datasource/src/commands/wikidata/mod.rs
+++ b/fantoir-datasource/src/commands/wikidata/mod.rs
@@ -7,6 +7,8 @@
use std::process::exit;
use oxrdf::Term;
+use sparql_client::{is_term_empty, parse_literal, parse_term_uri};
+use sparql_client::Client as SparqlClient;
use sqlx::PgPool;
use crate::commands::wikidata::qualification::determine_p31_winner;
@@ -15,7 +17,7 @@
use crate::WikidataArgs;
use crate::fantoir::{fix_fantoir_code, FixedFantoirCode};
use crate::services::query::search_fantoir_code;
-use crate::services::sparql::*;
+use crate::services::http_client::build_http_client;
pub static WIKIDATA_TABLE: &'static str = "fantoir_wikidata";
pub static WIKIDATA_SPARQL_ENDPOINT: &'static str = "https://query.wikidata.org/sparql";
@@ -38,7 +40,10 @@
}
// Query Wikidata and get (Wikidata/FANTOIR code, list of P31 (instance of) values) hashmap
- let client = Client::new(WIKIDATA_SPARQL_ENDPOINT);
+ let client = SparqlClient::from_http_client(
+ WIKIDATA_SPARQL_ENDPOINT,
+ build_http_client()
+ );
let mut what_map = HashMap::new();
client.query(include_str!("../../queries/wikidata.sparql"))
diff --git a/fantoir-datasource/src/services/http_client.rs b/fantoir-datasource/src/services/http_client.rs
--- a/fantoir-datasource/src/services/http_client.rs
+++ b/fantoir-datasource/src/services/http_client.rs
@@ -1,20 +1,29 @@
-use std::io::Error as IOError;
-use std::path::Path;
+//! Build a HTTP client with proper user agent.
+use std::collections::HashMap;
+
+use ds_http_client::{parse_headers, Client};
use lazy_static::lazy_static;
-use reqwest::Client as ReqwestClient;
-use reqwest::ClientBuilder;
-use reqwest::Error as ReqwestError;
-use reqwest::IntoUrl;
-use reqwest::Response;
-use reqwest::header::HeaderMap;
-use tokio::fs::File;
-use tokio::io::AsyncWriteExt;
+
+/* -------------------------------------------------------------
+ HTTP client
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+pub fn build_http_client () -> Client {
+ let mut headers = HashMap::new();
+ headers.insert(
+ "User-Agent".to_string(),
+ get_user_agent().to_string(),
+ );
+ let headers = parse_headers(headers);
+
+ Client::new(Some(headers))
+}
/* -------------------------------------------------------------
User agent
- The USER_AGENT variable is computed at build time.
+ Compute at build time user agent to use in HTTP requests.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
lazy_static! {
@@ -24,77 +33,6 @@
);
}
-pub fn get_user_agent () -> &'static str {
+fn get_user_agent () -> &'static str {
&USER_AGENT
}
-
-/* -------------------------------------------------------------
- HTTP client
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
-pub struct Client {
- client: ReqwestClient,
-}
-
-impl Client {
- pub fn new(headers: Option<HeaderMap>) -> Self {
- let headers = headers
- .unwrap_or(HeaderMap::new());
-
- let client = ClientBuilder::new()
- .user_agent(get_user_agent())
- .default_headers(headers)
- .gzip(true)
- .deflate(true)
- .build()
- .expect("Can't build HTTP client");
-
- Self {
- client,
- }
- }
-
- pub async fn get<T>(&self, url: T) -> Result<Response, Error>
- where T: IntoUrl {
- self.client
- .get(url)
- .send()
- .await
- .map_err(|error| Error::Reqwest(error))
- }
-
- pub async fn download<P, T>(&self, url: T, target_path: P) -> Result<usize, Error>
- where T: IntoUrl, P: AsRef<Path> {
- let mut file = File::create(target_path)
- .await
- .map_err(|error| Error::IO(error))?;
-
- let mut target_content = self.get(url).await?;
- let mut bytes_read = 0;
- while let Some(chunk) = target_content
- .chunk()
- .await
- .map_err(|error| Error::Reqwest(error))?
- {
- bytes_read += file.write(chunk.as_ref())
- .await
- .map_err(|error| Error::IO(error))?;
- }
-
- Ok(bytes_read)
- }
-}
-
-/* -------------------------------------------------------------
- HTTP client error
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
-/// HTTP client error
-#[derive(Debug)]
-pub enum Error {
- /// Represents an underlying error from Reqwest HTTP client when processing a request.
- Reqwest(ReqwestError),
-
- /// Represents an IO error when doing file operations.
- IO(IOError),
-}
diff --git a/fantoir-datasource/src/services/mod.rs b/fantoir-datasource/src/services/mod.rs
--- a/fantoir-datasource/src/services/mod.rs
+++ b/fantoir-datasource/src/services/mod.rs
@@ -1,3 +1,2 @@
pub mod query;
pub mod http_client;
-pub mod sparql;
diff --git a/fantoir-datasource/src/services/sparql.rs b/fantoir-datasource/src/services/sparql.rs
deleted file mode 100644
--- a/fantoir-datasource/src/services/sparql.rs
+++ /dev/null
@@ -1,136 +0,0 @@
-//! # SPARQL client
-
-use std::collections::HashMap;
-use std::io::BufRead;
-
-use oxrdf::Term;
-use reqwest::header::{HeaderMap, HeaderValue};
-use reqwest::Url;
-use sparesults::*;
-
-use crate::services::http_client::Client as HttpClient;
-
-type SparqlSolution = HashMap<String, Term>;
-
-/// SPARQL client
-pub struct Client {
- pub endpoint: String,
- http_client: HttpClient,
-}
-
-/// Represent results for a SPARQL query
-/// A query can return a collection of solutions or a boolean.
-pub enum SparqlResults {
- /// Results for SELECT queries
- Solutions(Vec<SparqlSolution>),
-
- /// Results for INSERT DATA, UPDATE DATA, etc. queries
- Boolean(bool),
-}
-
-impl Client {
- pub fn new (endpoint: &str) -> Self {
- let mut headers = HeaderMap::new();
- headers.insert("Accept", HeaderValue::from_static("Accept: application/sparql-results+xml"));
-
- Self {
- endpoint: String::from(endpoint),
- http_client: HttpClient::new(Some(headers)),
- }
- }
-
- pub async fn query (&self, query: &str) -> SparqlResults {
- let url = Url::parse_with_params(&self.endpoint, &[("query", query)])
- .expect("Can't parse endpoint as absolute URL.");
-
- let query_results = self.http_client.get(url).await
- .expect("Can't query endpoint")
- .text().await
- .expect("End-point didn't return a reply.");
-
- parse_sparql_results(&query_results)
- }
-}
-
-pub fn parse_sparql_results (query_results: &str) -> SparqlResults {
- let results_reader = get_query_results_xml_reader(query_results.as_bytes());
-
- SparqlResults::read(results_reader)
-}
-
-impl SparqlResults {
- pub fn read<T>(reader: QueryResultsReader<T>) -> Self
- where T: BufRead
- {
- match reader {
- QueryResultsReader::Solutions(solutions) => {
- Self::Solutions(parse_sparql_solutions(solutions))
- },
- QueryResultsReader::Boolean(bool) => Self::Boolean(bool),
- }
- }
-
- pub fn into_solutions (self) -> Option<Vec<SparqlSolution>> {
- match self {
- SparqlResults::Solutions(solutions) => Some(solutions),
- SparqlResults::Boolean(_) => None,
- }
- }
-}
-
-fn get_query_results_xml_reader<T>(reader: T) -> QueryResultsReader<T>
- where T: BufRead
-{
- QueryResultsParser::from_format(QueryResultsFormat::Xml)
- .read_results(reader)
- .expect("Can't read SPARQL results")
-}
-
-fn parse_sparql_solutions<T> (solutions: SolutionsReader<T>) -> Vec<SparqlSolution>
- where T: BufRead
-{
- solutions
- .map(|solution| {
- parse_sparql_result(
- solution.expect("Can't read solution")
- )
- })
- .collect()
-}
-
-pub fn parse_sparql_result (solution: QuerySolution) -> SparqlSolution {
- solution
- .iter()
- .map(|(variable, term)| (
- variable.as_str().to_string(),
- term.clone(),
- ))
- .collect()
-}
-
-pub fn parse_term_uri (term: &Term) -> Option<String> {
- if let Term::NamedNode(node) = term {
- Some(node.as_str().to_string())
- } else {
- None
- }
-}
-
-pub fn parse_literal (term: &Term) -> Option<String> {
- if let Term::Literal(literal) = term {
- Some(literal.value().to_string())
- } else {
- None
- }
-}
-
-pub fn is_term_empty(term: &Term) -> bool {
- match term {
- Term::NamedNode(node) => {
- // Special values IRI are considered as empty values.
- node.as_str().contains("/.well-known/genid/")
- }
- Term::BlankNode(_) => true,
- Term::Literal(_) => false,
- }
-}
diff --git a/sparql-client/Cargo.toml b/sparql-client/Cargo.toml
new file mode 100644
--- /dev/null
+++ b/sparql-client/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "sparql-client"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+ds-http-client = { version = "0.1.0", path = "../ds-http-client" }
+oxrdf = "~0.1.7"
+lazy_static = "~1.5.0"
+tokio = "~1.39.3"
+
+[dependencies.reqwest]
+version = "~0.12.7"
+features = ["gzip", "deflate"]
+
+[dependencies.sparesults]
+version = "~0.1.8"
+features = ["rdf-star"]
diff --git a/sparql-client/src/lib.rs b/sparql-client/src/lib.rs
new file mode 100644
--- /dev/null
+++ b/sparql-client/src/lib.rs
@@ -0,0 +1,252 @@
+//! # SPARQL client
+
+use std::collections::HashMap;
+use std::io::BufRead;
+
+use ds_http_client::Client as HttpClient;
+use lazy_static::lazy_static;
+use oxrdf::Term;
+use reqwest::header::{HeaderMap, HeaderValue};
+use reqwest::Url;
+use sparesults::*;
+
+type SparqlSolution = HashMap<String, Term>;
+
+/* -------------------------------------------------------------
+ SPARQL client
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/// SPARQL client
+pub struct Client {
+ pub endpoint: String,
+ http_client: HttpClient,
+}
+
+impl Client {
+ pub fn new (endpoint: &str) -> Self {
+ let mut headers = HeaderMap::new();
+ headers.insert("User-Agent", HeaderValue::from_static(get_user_agent()));
+ let http_client = HttpClient::new(Some(headers));
+
+ Self::from_http_client(endpoint, http_client)
+ }
+
+ pub fn from_http_client(endpoint: &str, http_client: HttpClient) -> Self {
+ Self {
+ endpoint: String::from(endpoint),
+ http_client,
+ }
+ }
+
+ pub async fn query (&self, query: &str) -> SparqlResults {
+ let url = Url::parse_with_params(&self.endpoint, &[("query", query)])
+ .expect("Can't parse endpoint as absolute URL.");
+ let headers = self.get_query_headers();
+
+ let query_results = self.http_client
+ .get_with_headers(url, headers).await
+ .expect("Can't query endpoint")
+ .text().await
+ .expect("End-point didn't return a reply.");
+
+ parse_sparql_results(&query_results)
+ }
+
+ fn get_query_headers(&self) -> HashMap<String, String> {
+ let mut headers = HashMap::new();
+ headers.insert("Accept".to_string(), "Accept: application/sparql-results+xml".to_string());
+
+ headers
+ }
+}
+
+/* -------------------------------------------------------------
+ SPARQL query results
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/// Represent results for a SPARQL query
+/// A query can return a collection of solutions or a boolean.
+pub enum SparqlResults {
+ /// Results for SELECT queries
+ Solutions(Vec<SparqlSolution>),
+
+ /// Results for INSERT DATA, UPDATE DATA, etc. queries
+ Boolean(bool),
+}
+
+impl SparqlResults {
+ pub fn read<T>(reader: QueryResultsReader<T>) -> Self
+ where
+ T: BufRead
+ {
+ match reader {
+ QueryResultsReader::Solutions(solutions) => {
+ Self::Solutions(parse_sparql_solutions(solutions))
+ },
+ QueryResultsReader::Boolean(bool) => Self::Boolean(bool),
+ }
+ }
+
+ pub fn into_solutions(self) -> Option<Vec<SparqlSolution>> {
+ match self {
+ SparqlResults::Solutions(solutions) => Some(solutions),
+ SparqlResults::Boolean(_) => None,
+ }
+ }
+
+ pub fn into_bool(self) -> Option<bool> {
+ match self {
+ SparqlResults::Solutions(_) => None,
+ SparqlResults::Boolean(bool) => Some(bool),
+ }
+ }
+}
+
+pub fn parse_sparql_results (query_results: &str) -> SparqlResults {
+ let results_reader = get_query_results_xml_reader(query_results.as_bytes());
+
+ SparqlResults::read(results_reader)
+}
+
+fn get_query_results_xml_reader<T>(reader: T) -> QueryResultsReader<T>
+where T: BufRead
+{
+ QueryResultsParser::from_format(QueryResultsFormat::Xml)
+ .read_results(reader)
+ .expect("Can't read SPARQL results")
+}
+
+fn parse_sparql_solutions<T> (solutions: SolutionsReader<T>) -> Vec<SparqlSolution>
+where T: BufRead
+{
+ solutions
+ .map(|solution| {
+ parse_sparql_result(
+ solution.expect("Can't read solution")
+ )
+ })
+ .collect()
+}
+
+pub fn parse_sparql_result (solution: QuerySolution) -> SparqlSolution {
+ solution
+ .iter()
+ .map(|(variable, term)| (
+ variable.as_str().to_string(),
+ term.clone(),
+ ))
+ .collect()
+}
+
+pub fn parse_term_uri (term: &Term) -> Option<String> {
+ if let Term::NamedNode(node) = term {
+ Some(node.as_str().to_string())
+ } else {
+ None
+ }
+}
+
+pub fn parse_literal (term: &Term) -> Option<String> {
+ if let Term::Literal(literal) = term {
+ Some(literal.value().to_string())
+ } else {
+ None
+ }
+}
+
+/* -------------------------------------------------------------
+ Helper methods
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+pub fn is_term_empty(term: &Term) -> bool {
+ match term {
+ Term::NamedNode(node) => {
+ // Special values IRI are considered as empty values.
+ node.as_str().contains("/.well-known/genid/")
+ }
+ Term::BlankNode(_) => true,
+ Term::Literal(_) => false,
+ Term::Triple(triple) => is_term_empty(&triple.object),
+ }
+}
+
+/* -------------------------------------------------------------
+ User agent
+
+ The USER_AGENT variable is computed at build time.
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+lazy_static! {
+ pub static ref USER_AGENT: String = format!(
+ "{}/{}",
+ env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")
+ );
+}
+
+/// Gets the default user agent
+pub fn get_user_agent () -> &'static str {
+ &USER_AGENT
+}
+
+/* -------------------------------------------------------------
+ Tests
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ pub fn test_parse_solution_results() {
+ let solutions_result = r#"
+<?xml version="1.0"?>
+<sparql xmlns="http://www.w3.org/2005/sparql-results#">
+ <head>
+ <variable name="foo"/>
+ </head>
+ <results>
+ <result>
+ <binding name="foo">
+ <literal xml:lang="en">bar</literal>
+ </binding>
+ </result>
+ </results>
+</sparql>
+ "#;
+
+ let results = parse_sparql_results(solutions_result);
+ let actual = results.into_solutions();
+
+ assert!(actual.is_some());
+
+ let solutions = actual.unwrap();
+ assert_eq!(1, solutions.iter().count());
+
+ let solution = solutions.first().unwrap();
+ assert_eq!(1, solution.iter().count());
+
+ // Asserts solution can be parsed as foo=bar
+ assert!(solution.contains_key("foo"));
+ let term = &solution["foo"];
+ assert!(term.is_literal());
+ let actual = parse_literal(term).unwrap();
+ assert!(actual.eq("bar"));
+ }
+
+ #[test]
+ pub fn test_parse_boolean_results () {
+ let boolean_results = r#"
+<?xml version="1.0"?>
+<sparql xmlns="http://www.w3.org/2005/sparql-results#">
+ <head />
+ <boolean>true</boolean>
+</sparql>
+ "#;
+
+ let results = parse_sparql_results(boolean_results);
+ let actual = results.into_bool();
+
+ assert!(actual.is_some());
+ assert!(actual.unwrap());
+ }
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Nov 24, 05:58 (10 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2259062
Default Alt Text
D3424.id8841.diff (23 KB)
Attached To
Mode
D3424: Extract SPARQL client
Attached
Detach File
Event Timeline
Log In to Comment