(Rust + Rocket + Tera) × プロデュース = im@sparql_details

github.com

タイトル通り、Rust言語とそのCrateであるRocket、テンプレートエンジンのTeraを使ってim@sparqlのdetailsのアレ(特に名前がないゆえの表現)を置き換えました。

アレの例→Kisaragi_Chihaya detail

Rust

Rust言語については特に大丈夫ですかね。
コンパイル時のチェックが厳しいでお馴染みのシステムプログラミング言語ですね。
C++に足を浸けてる人間としては、今後学びたい言語でした。    ja.wikipedia.org

Rocket

rocket.rs

Rustで使えるWebフレームワークです。初学者にも満たないので、Webフレームワークって何ができればWebフレームワークなのか分かりませんが、ルーティングとかフォーム入力とか当たり前の機能はあるみたいです。
なんだこの頭悪い説明は...

Tera

tera.netlify.app

Rocketで使えるテンプレートエンジンの一つです。

Inspired by Jinja2 and Django templates

ですって。

やったこと

最初にも例出したIchikawa_Hinana detailの実装を置き換えました。
今まではNode.jsとexpressだったのですが、Rustを勉強するために差し替えた次第です。
なお、見た目は全く変わってないのでユーザーには特に益はありません。

内容

Rustでも非常に簡単に書けました。126行のうち、半分くらいはJSONのデシリアライズなのでめっちゃ少ないです。

JSONシリアライズ部(展開可)

#[derive(Debug, Deserialize, Serialize)]
struct N {
  r#type: String,
  value: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct O {
  r#type: String,
  #[serde(default)]
  datatype: String,
  #[serde(default, rename = "xml:lang")]
  xml_lang: String,
  value: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct Bindings {
  n: N,
  o: O,
}
#[derive(Debug, Deserialize)]
struct Results {
  bindings: Vec<Bindings>,
}
#[derive(Debug, Deserialize)]
struct Head {
  vars: Vec<String>,
}
#[derive(Debug, Deserialize)]
struct Response {
  head: Head,
  results: Results,
}
#[derive(Serialize)]
struct MessageContent {
  title: String,
  num: usize,
  json: Vec<Bindings>,
}

im@sparqlのJSONの結果を見たことある人は納得の構造体群ですね。
ちなみにクエリは

PREFIX schema: <http://schema.org/>
PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/RDFs/detail/>
SELECT * WHERE {
  { imas:{} ?n ?o;}
}order by (?n)

です。{}には適切な主語が入ります。

処理部(展開可)

#[get("/<subject>")]
fn get_data(subject: String) -> Template {
  const FRAGMENT: &AsciiSet = &CONTROLS;
  let encoded_subject = utf8_percent_encode(&subject, FRAGMENT).to_string();
  let quety = format!("PREFIX schema: <http://schema.org/>PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/RDFs/detail/>SELECT * WHERE {{ imas:{} ?n ?o;}}order by (?n)", encoded_subject);
  let encoded_query = form_urlencoded::Serializer::new(String::new())
    .append_pair("output", "json")
    .append_pair("force-accept", "text/plain")
    .append_pair("query", &quety)
    .finish();
  let base_url = format!(
    "https://sparql.crssnky.xyz/spql/imas/query?{}",
    encoded_query
  );
  let res = ureq::get(&base_url).call();

  if res.ok() {
    let json_str = res.into_string().unwrap();
    let res_json: Response = serde_json::from_str(&json_str).unwrap();
    let mut json = res_json.results.bindings;
    let json_num = json.len();
    if json_num > 0 {
      for data in &mut json {
        match &*(data.n.value) {
          "http://schema.org/memberOf" => {
            data.o.value = percent_decode(data.o.value.as_bytes())
              .decode_utf8()
              .unwrap()
              .to_string();
          }
          "http://schema.org/owns" => {
            data.o.value = percent_decode(data.o.value.as_bytes())
              .decode_utf8()
              .unwrap()
              .to_string();
          }
          _ => (),
        }
      }
      let content = MessageContent {
        title: subject,
        num: json_num,
        json: json,
      };
      return Template::render("detail", &content);
    }
  }
  let content = MessageContent {
    title: subject,
    num: 0,
    json: vec![],
  };
  Template::render("error", &content)
}

キモなのは#[get("/<subject>")]fn get_data(subject: String)でしょうか。いわゆるルーティングの文字列を引数として取得しています。これをすることで、URLhttps://sparql.crssnky.xyz/imasrdf/RDFs/detail/の後ろに主語が続くと適切に処理されます。
引数として取得した文字列は、そのままクエリの主語の部分に連結されます。そしてクエリを送って結果をデシリアライズします。
im@sparql特有の処理として、for文の部分があります。一部の主語は日本語をそのままURLエンコードしたものをつっこんでいるので、表示には適しません。なので、テンプレートエンジンに送る前にデコードしてあげています。
それ以外はそのままのJSONをテンプレートに渡して描画しています。

こんな感じで、簡単にWebフレームワークを使ったRustを書けます。みなさんも安全性に守られたコーディングしませんか。