1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#![deny(missing_docs)]
//#![deny(missing_doc_example)] <-- for later, when I'm swole

//! This module is the main module for the Ligature project.
//! It represents to common types and traits used by Ligature.

/// A string that represents a Dataset's Name.
/// Currently can only be ASCII text separated by /
/// TODO add validator and tests
#[derive(Debug)]
pub struct DatasetName(String);

/// A node that is only identified by a unique u64 id.
#[derive(Debug)]
pub struct BlankNode(u64);

/// An IRI is represented via https://www.ietf.org/rfc/rfc3987.txt
/// TODO add validator and tests
#[derive(Debug)]
pub struct IRI(String);

/// A unit struct used to represent the concept of a Default Graph in a quad store.
#[derive(Debug)]
pub struct DefaultGraph;

/// A wrapper type that represents a language tag.
/// Represented via `[a-zA-Z]+ ('-' [a-zA-Z0-9]+)*`
/// TODO add validator and tests
#[derive(Debug)]
pub struct LangTag(String);

/// A struct containing text and a language tag that denotes what language the text is expressed in.
/// TODO add validator and tests
#[derive(Debug)]
pub struct LangLiteral {
    /// The String literal that is represented by this LangLiteral
    pub value: String,
    /// The LangTag that represents the language used to express the value.
    pub lang_tag: LangTag,
}

/// A struct containing a value represented as a String and the type of the value represented by an IRI.
/// TODO add validator and tests
/// TODO probably need a function that double checks a given UnknownLiteral is actually unknown
#[derive(Debug)]
pub struct UnknownLiteral {
    /// The value of this literal represented as a String.
    pub value: String,
    /// The IRI that represents this type.
    pub r#type: IRI,
}

/// An enum that represents all the currently supported literal types.
#[derive(Debug)]
pub enum Literal {
    /// A tagged LangLiteral used for an RDF literal
    LangLiteral(LangLiteral),
    /// A tagged String used for an RDF literal
    StringLiteral(String),
    /// A tagged bool used for an RDF literal
    BooleanLiteral(bool),
    /// A tagged i64 used for an RDF literal
    LongLiteral(i64),
    /// A tagged f64 used for an RDF literal
    DoubleLiteral(f64),
    /// A tagged UnknownLiteral used for an RDF literal
    UnknownLiteral(UnknownLiteral),
}

/// A set of enums used to express range queries when it makes sense for that type (ie no support for BooleanLiteralRange or UnknownLiteralRange since they don't make sense).
#[derive(Debug)]
pub enum Range {
    /// Represents a range of LangLiterals
    /// Note: LangTag needs to match for any comparison to take place.
    LangLiteralRange {
        /// The starting LangLiteral (inclusive)
        start: LangLiteral,
        /// The end LangLiteral (exclusive)
        end: LangLiteral,
    },
    /// Represents a String range using basic String comparisons.
    StringLiteralRange {
        /// The starting String (inclusive)
        start: String,
        /// The end String (exclusive)
        end: String,
    },
    /// Represents a String range using basic i64 comparisons.
    LongLiteralRange {
        /// The starting i64 (inclusive)
        start: i64,
        /// The end i64 (exclusive)
        end: i64,
    },
    /// Represents a String range using basic f64 comparisons.
    DoubleLiteralRange {
        /// The starting f64 (inclusive)
        start: f64,
        /// The end f64 (exclusive)
        end: f64,
    },
}

/// The set of valid types that can be used as a Subject.
/// TODO add validator and tests
#[derive(Debug)]
pub enum Subject {
    /// A tagged IRI used as a Subject.
    IRI(IRI),
    /// A tagged BlankNode used as a Subject.
    BlankNode(BlankNode),
    /// A tagged DefaultGraph used as a Subject.
    DefaultGraph(DefaultGraph),
}

/// The set of valid types that can be used as a Predicate.
/// TODO add validator and tests
#[derive(Debug)]
pub enum Predicate {
    /// A tagged IRI used as a Predicate.
    IRI(IRI),
}

/// The set of valid types that can be used as an Object.
/// TODO add validator and tests
#[derive(Debug)]
pub enum Object {
    /// A tagged Subject used as an Object.
    Subject(Subject),
    /// A tagged Literal used as an Object.
    Literal(Literal),
}

/// The set of valid types that can be used as a Graph name.
/// TODO add validator and tests
#[derive(Debug)]
pub enum Graph {
    /// A tagged IRI used as a Graph.
    IRI(IRI),
    /// A tagged BlankNode used as a Graph.
    BlankNode(BlankNode),
    /// A tagged DefaultGraph used as a Graph.
    DefaultGraph(DefaultGraph),
}

/// A Statement is a grouping of Subject, Predicate, and Object.
/// TODO add validator and tests
#[derive(Debug)]
pub struct Statement {
    /// The Subject of a Statement
    pub subject: Subject,
    /// The Predicate of a Statement
    pub predicate: Predicate,
    /// The Object of a Statement
    pub object: Object,
}

/// A PersistedStatement is a Statement along with a DatasetName and Graph that that Statement belongs to.
/// TODO add validator and tests
#[derive(Debug)]
pub struct PersistedStatement {
    /// The Dataset this Statement is persisted in
    pub dataset: DatasetName,
    /// The Statement that is persisted
    pub statement: Statement,
    /// The Graph this Statement is persisted in
    pub graph: Graph,
}

//val a: IRI = IRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type").getOrElse(???)
//fn validLangTag(langTag: String) -> Boolean =
//  "[a-zA-Z]+(-[a-zA-Z0-9]+)*".r.matches(langTag)

/// A general struct for representing errors involving Ligature.
/// TODO should probably be an enum with a bunch of specific cases
#[derive(Debug)]
pub struct LigatureError(String);

/// A trait that all Ligature implementations implement.
pub trait Ligature {
    /// Returns all Datasets in a Ligature instance.
    fn all_datasets(&self) -> Box<dyn Iterator<Item = DatasetName>>;

    /// Returns all Datasets in a Ligature instance that start with the given prefix.
    fn match_datasets(&self, prefix: String) -> Box<dyn Iterator<Item = DatasetName>>;

    /// Returns all Datasets in a Ligature instance that are in a given range (inclusive, exclusive].
    fn match_datasets_range(
        &self,
        start: String,
        end: String,
    ) -> Box<dyn Iterator<Item = DatasetName>>;

    /// Creates a dataset with the given name.
    /// TODO should probably return its own error type { InvalidDatasetName, DatasetExists, CouldNotCreateDataset }
    fn create_dataset(&self, dataset: DatasetName) -> Result<(), LigatureError>;

    /// Deletes a dataset with the given name.
    /// TODO should probably return its own error type { InvalidDatasetName, CouldNotDeleteDataset }
    fn delete_dataset(&self, dataset: DatasetName) -> Result<DatasetName, LigatureError>;

    /// Initiazes a QueryTx
    /// TODO should probably return its own error type CouldNotInitializeQueryTx
    fn query(&self, dataset: DatasetName) -> Result<Box<dyn QueryTx>, LigatureError>;

    /// Initiazes a WriteTx
    /// TODO should probably return its own error type CouldNotInitializeWriteTx
    fn write(&self, dataset: DatasetName) -> Result<Box<dyn WriteTx>, LigatureError>;
}

/// Represents a QueryTx within the context of a Ligature instance and a single Dataset
pub trait QueryTx {
    /// Returns all Statements in this Dataset as PersistedStatements.
    /// TODO should probably return a Result
    fn all_statements(&self) -> Box<dyn Iterator<Item = PersistedStatement>>;

    /// Retuns all Statements that match the given criteria.
    /// If a parameter is None then it matches all, so passing all Nones is the same as calling all_statements.
    /// TODO should return a Result
    fn match_statements(
        &self,
        subject: Option<Subject>,
        predicate: Option<Predicate>,
        object: Option<Object>,
        graph: Option<Graph>,
    ) -> Box<dyn Iterator<Item = PersistedStatement>>;

    /// Retuns all Statements that match the given criteria.
    /// If a parameter is None then it matches all.
    /// TODO should return a Result
    fn match_statements_range(
        &self,
        subject: Option<Subject>,
        predicate: Option<Predicate>,
        graph: Option<Graph>,
        range: Range,
    ) -> Box<dyn Iterator<Item = PersistedStatement>>;
}

/// Represents a WriteTx within the context of a Ligature instance and a single Dataset
pub trait WriteTx {
    /// Creates a new, unique BlankNode within this Dataset.
    /// Note: BlankNodes are shared across named graphs in a given Dataset.
    fn new_blank_node(&self) -> Result<BlankNode, LigatureError>;

    /// Adds a given Statement to this Dataset.
    /// If the Statement already exists nothing happens.
    /// Note: Potentally could trigger a ValidationError
    fn add_statement(
        &self,
        statement: Statement,
        graph: Graph,
    ) -> Result<PersistedStatement, LigatureError>;

    /// Removes a given Statement from this Dataset.
    /// If the Statement doesn't exist nothing happens.
    /// Note: Potentally could trigger a ValidationError.
    fn remove_statement(
        &self,
        statement: Statement,
        graph: Graph,
    ) -> Result<Statement, LigatureError>;

    /// Cancels this transaction so that none of the changes made so far will be stored.
    /// This also closes this transaction so no other methods can be called.
    fn cancel(&self) -> Result<(), LigatureError>;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn valid_dataset_names() {
        //   assert(!validNamedNode(NamedNode("")))
        //   assert(validNamedNode(NamedNode("http://localhost/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost(/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost{/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost\\/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost</people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost>/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost[/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost]/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost\"/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost'/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost`/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost\t/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost\n/people/7")))
        //   assert(!validNamedNode(NamedNode("http://localhost /people/7")))
        //   assert(validNamedNode(NamedNode("hello")))
        //   assert(validNamedNode(NamedNode("_:")))
        //   assert(validNamedNode(NamedNode("_:valid")))
        //   assert(validNamedNode(NamedNode("_:1")))
        //   assert(validNamedNode(NamedNode("_:1344")))
    }

    #[test]
    fn valid_lang_tags() {
        // assert(!validLangTag(""))
        // assert(validLangTag("en"))
        // assert(!validLangTag("en-"))
        // assert(validLangTag("en-fr"))
        // assert(!validLangTag("en-fr-"))
        // assert(validLangTag("en-fr-sp"))
        // assert(validLangTag("ennnenefnk-dkfjkjfl-dfakjelfkjalkf-fakjeflkajlkfj"))
        // assert(!validLangTag("en-fr-ef "))
    }
}