Simple "Basic Post" Schema
This example shows how to create a Rust application with rebase that uses TreeLDR to define a simple self-signed basic blog post credential schema. The program will consist of:
- a Rust type definition automatically derived from this layout, and
- an implementation of Rebase's
SchemaType
trait for this type written with the help of TreeLDR.
First, create a new Rust project using cargo:
cargo new basic-post-example
cd basic-post-example
The
src
directory will contain the sources of our application. For now, it only contains a main.rs
file with a dummy main function definition.In the
src
directory, create a new basic_post.tldr
file containing the following BasicPost
layout definition for https://schema.org/BlogPosting
:base <https://example.com/>;
use <http://www.w3.org/2000/01/rdf-schema#> as rdfs;
use <https://schema.org/> as schema;
use <https://treeldr.org/> as tldr;
/// Basic Blog Post.
layout BasicPost for schema:BlogPosting {
/// Identifier of the post.
//
// We use the `tldr:self` property that reflects each value.
// By using the `&rdfs:Resource`, a reference to a resource,
// we ensure that `id` will hold a reference to itself.
// In other word, `id` will be the identifier of the post itself.
tldr:self as id: required &rdfs:Resource,
/// Title of the post.
schema:title: schema:Text,
/// Content of the post.
schema:body: schema:Text
}
Instead of defining the
schema:BlogPosting
type (and the schema:Text
type) ourselves in this file, we will rely on an example file provided in the TreeLDR repository that contains schema.org
type definitions. In the terminal, use the following command to download this file as src/schema.org.tldr
:curl https://raw.githubusercontent.com/spruceid/treeldr/main/examples/schema.org.tldr > src/schema.org.tldr
We will embed the previous layout definition into the Rust program as a type definition using the
#[tldr]
procedural macro attribute provided by the treeldr-rust-macros
crate. This crate relies on the treeldr-rust-prelude
crate. First, add those two crates to the dependencies of the Rust program by adding the following lines to the Cargo.toml
file under the [dependencies]
section:treeldr-rust-macros = { git = "https://github.com/spruceid/treeldr.git" }
treeldr-rust-prelude = { git = "https://github.com/spruceid/treeldr.git" }
Then add the following module definition annotated with the
#[tldr]
procedural macro attribute to the src/main.rs
file:#[tldr(
"src/schema.org.tldr",
"src/basic_post.tldr"
)]
mod schema {
#[prefix("https://schema.org/")]
pub mod org {}
#[prefix("https://example.com/")]
pub mod example {}
}
The arguments to the
#[tldr]
macro lists all the files we want to include in the Rust program in the schema
module. The submodules annotated with the #[prefix]
macro specify where to put the types. Here every layout prefixed by https://schema.org/
will be put inside the schema::org
module, while the layouts prefixed by https://example.com/
will be put inside the schema::example
module.At compile time, this macro call will expand to the following module:
mod schema {
pub mod org {
pub type Text = ::std::alloc::String;
}
pub mod example {
pub struct BasicPost {
id: Id,
title: Option<super::org::Text>,
body: Option<super::org::Text>
}
}
}
The
schema::example::BasicPost
type corresponds to our blog post schema layout.We now need to implement rebase's
SchemaType
for BasicPost
. In the src/main.rs
file, add the following implementation:use ssi::{one_or_many::OneOrMany, vc::Evidence};
use rebase::schema::schema_type::{SchemaError, SchemaType};
impl SchemaType for schema::example::BasicPost {
fn context(&self) -> Result<serde_json::Value, SchemaError> {
todo!()
}
fn types(&self) -> Result<Vec<String>, SchemaError> {
Ok(vec![
"VerifiableCredential".to_string(),
"VerifiableBasicPost".to_string()
])
}
fn subject(&self) -> Result<serde_json::Value, SchemaError> {
Ok(self.into())
}
fn evidence(&self) -> Result<Option<OneOrMany<Evidence>>, SchemaError> {
Ok(None)
}
}
The implementation is almost complete. The only missing piece is the
context
method implementation that should return the JSON-LD context of the final verifiable credential (VC). This VC is a VerifiableBasicPost
as stated in the types
method, whose subject is the blog post itself as stated in the subject
method. We can generate this JSON-LD context using TreeLDR by first defining what the final VC type will be. Create a new src/vc.tldr
file with the following content:base <https://example.com/>;
use <https://schema.org/> as schema;
use <https://www.w3.org/2018/credentials#> as vc;
type VerifiableBasicPost =
vc:VerifiableCredential &
all vc:credentialSubject: (schema:BlogPosting with BasicPost);
We define the
VerifiableBasicPost
as a vc:VerifiableCredential
where the credential subject is a schema:BlogPosting
. The with
keyword is used to specify that we will use the BasicPost
layout to represent a schema:BlogPosting
in the VC. Once again, we will use a TreeLDR example file to define the vc:VerifiableCredential
type. Use the following command to download the file as src/vc.tldr
:curl https://raw.githubusercontent.com/spruceid/treeldr/main/examples/vc.tldr > src/vc.tldr
We can now generate the JSON-LD context using
tldrc
and the following command:tldrc -i src/schema.org.tldr -i src/vc.tldr -i src/basic_post.tldr json-ld-context -c https://www.w3.org/2018/credentials/v1 https://example.com/VerifiableBasicPost
[
"https://www.w3.org/2018/credentials/v1",
{
"title": "https://schema.org/title",
"body": "https://schema.org/body",
"VerifiableBasicPost": "https://example.com/VerifiableBasicPost"
}
]
The
https://www.w3.org/2018/credentials/v1
context defines the VerifiableCredential
type. The result can be used to define the context
method. Replace the todo!()
in the previous implementation with the following piece of code that includes the JSON-LD context generated thanks to TreeLDR.Ok(serde_json::json!([
"https://www.w3.org/2018/credentials/v1",
{
"title": "https://schema.org/title",
"body": "https://schema.org/body",
"VerifiableBasicPost": "https://example.com/VerifiableBasicPost"
}
]))
Last modified 1yr ago