Compare commits

...

14 Commits

Author SHA1 Message Date
Tristan B. Velloza Kildaire e78b201aea libpb
- Require minimum version `0.1.2` of `jstruct`
2023-06-18 13:33:54 +02:00
Tristan B. Velloza Kildaire aa77afccac - Added logo 2023-01-09 12:21:52 +02:00
Tristan B. Velloza Kildaire da22ae1c61 - Now using `jstruct` for serialization/deserialization (see Hax-io/jstruct for details)
- Updated .gitignore to exclude dub.selections.json
2023-01-09 11:19:11 +02:00
Tristan B. Velloza Kildaire 4fc4242c1e - Moved back to more clean templating method (for now till a fix can be found for the type lookups for issue #7) 2023-01-09 10:28:56 +02:00
Tristan B. Velloza Kildaire c89d6bd8b6 Updated .gitignore 2023-01-08 13:36:28 +02:00
Tristan B. Velloza Kildaire 707a1b9373 - `fromJSON()` now throws `RemoteFieldMissing` exception if the input JSON does not have a field found in the input struct type `RecordType`
- Added a unittest to test the above
2023-01-08 13:36:01 +02:00
Tristan B. Velloza Kildaire 3d9b9a4976 - Attempt to mixin properly 2023-01-06 16:42:40 +02:00
Tristan B. Velloza Kildaire b10c7d928d Attempt at fixing enum type lookups (anything not implicitly in scope of mixin such as built-in types) 2023-01-06 16:38:34 +02:00
Tristan B. Velloza Kildaire 2f5710fafb - Removed uneeded public imports 2023-01-06 16:38:03 +02:00
Tristan B. Velloza Kildaire d89b7159dd - Ensure that `serializeRecord()` and `fromJSON()` are publically imported as part of the package import to fix user-defined types lookups (such as enums) 2023-01-06 15:30:59 +02:00
Tristan B. Velloza Kildaire 5c0dcfe15b Updated testing DB schema 2023-01-05 13:01:53 +02:00
Tristan B. Velloza Kildaire b9a2254a66 - Removed uneeded default arguments 2023-01-05 13:01:13 +02:00
Tristan B. Velloza Kildaire 1b3ef337a5 - Now `updateRecord()` is for base collections and `updateRecordAuth()` is for auth collections 2023-01-05 12:58:51 +02:00
Tristan B. Velloza Kildaire 571e8dea81 - Mixin template `MemberAndType` should be private (not user-accessibe) 2023-01-05 12:50:36 +02:00
11 changed files with 115 additions and 267 deletions

2
.gitignore vendored
View File

@ -14,3 +14,5 @@ libpb-test-*
*.o
*.obj
*.lst
liblibpb.a
dub.selections.json

View File

@ -1,3 +1,5 @@
![](branding/logo.png)
libpb
=====

BIN
branding/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
branding/logo.xcf Normal file

Binary file not shown.

View File

@ -3,9 +3,14 @@
"Tristan B. Velloza Kildaire"
],
"copyright": "Copyright © 2022, Tristan B. Velloza Kildaire",
"dependencies": {
"jstruct": ">=0.1.2"
},
"description": "PocketBase wrapper with serializer/deserializer support",
"libs": [
"curl"
],
"license": "LGPL v3.0",
"name": "libpb",
"targetType" : "library",
"libs":["curl"]
"targetType": "library"
}

View File

@ -46,7 +46,7 @@
"schema": [
{
"id": "psy7unkl",
"name": "bruh",
"name": "name",
"type": "text",
"system": false,
"required": false,
@ -56,6 +56,18 @@
"max": null,
"pattern": ""
}
},
{
"id": "jroziygp",
"name": "age",
"type": "number",
"system": false,
"required": false,
"unique": false,
"options": {
"min": null,
"max": null
}
}
],
"listRule": "",

View File

@ -1,144 +0,0 @@
module libpb.deserialization;
import std.json;
import std.traits : FieldTypeTuple, FieldNameTuple;
public RecordType fromJSON(RecordType)(JSONValue jsonIn)
{
RecordType record;
// Alias as to only expand later when used in compile-time
alias structTypes = FieldTypeTuple!(RecordType);
alias structNames = FieldNameTuple!(RecordType);
alias structValues = record.tupleof;
static foreach(cnt; 0..structTypes.length)
{
debug(dbg)
{
pragma(msg, structTypes[cnt]);
pragma(msg, structNames[cnt]);
// pragma(msg, structValues[cnt]);
}
static if(__traits(isSame, mixin(structTypes[cnt]), byte))
{
mixin("record."~structNames[cnt]) = cast(byte)jsonIn[structNames[cnt]].integer();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), ubyte))
{
mixin("record."~structNames[cnt]) = cast(ubyte)jsonIn[structNames[cnt]].uinteger();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), short))
{
mixin("record."~structNames[cnt]) = cast(short)jsonIn[structNames[cnt]].integer();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), ushort))
{
mixin("record."~structNames[cnt]) = cast(ushort)jsonIn[structNames[cnt]].uinteger();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), int))
{
mixin("record."~structNames[cnt]) = cast(int)jsonIn[structNames[cnt]].integer();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), uint))
{
mixin("record."~structNames[cnt]) = cast(uint)jsonIn[structNames[cnt]].uinteger();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), ulong))
{
mixin("record."~structNames[cnt]) = cast(ulong)jsonIn[structNames[cnt]].uinteger();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), long))
{
mixin("record."~structNames[cnt]) = cast(long)jsonIn[structNames[cnt]].integer();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), string))
{
mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]].str();
debug(dbg)
{
pragma(msg,"record."~structNames[cnt]);
}
}
else static if(__traits(isSame, mixin(structTypes[cnt]), JSONValue))
{
mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]];
debug(dbg)
{
pragma(msg,"record."~structNames[cnt]);
}
}
else static if(__traits(isSame, mixin(structTypes[cnt]), bool))
{
mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]].boolean();
debug(dbg)
{
pragma(msg,"record."~structNames[cnt]);
}
}
//FIXME: Not sure how to get array support going, very new to meta programming
else static if(__traits(isSame, mixin(structTypes[cnt]), mixin(structTypes[cnt])[]))
{
mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]].boolean();
debug(dbg)
{
pragma(msg,"record."~structNames[cnt]);
}
}
else
{
// throw new
//TODO: Throw error
debug(dbg)
{
pragma(msg, "Unknown type for de-serialization");
}
}
}
return record;
}
unittest
{
import std.string : cmp;
import std.stdio : writeln;
struct Person
{
public string firstname, lastname;
public int age;
public bool isMale;
public JSONValue obj;
public int[] list;
}
JSONValue json = parseJSON(`{
"firstname" : "Tristan",
"lastname": "Kildaire",
"age": 23,
"obj" : {"bruh":1},
"isMale": true,
"list": [1,2,3]
}
`);
Person person = fromJSON!(Person)(json);
debug(dbg)
{
writeln(person);
}
assert(cmp(person.firstname, "Tristan") == 0);
assert(cmp(person.lastname, "Kildaire") == 0);
assert(person.age == 23);
assert(person.isMale == true);
assert(person.obj["bruh"].integer() == 1);
//TODO: list test case
}

View File

@ -6,8 +6,7 @@ import std.net.curl;
import std.conv : to;
import std.string : cmp;
import libpb.exceptions;
import libpb.serialization;
import libpb.deserialization;
import jstruct : fromJSON, SerializationError, serializeRecord;
private mixin template AuthTokenHeader(alias http, PocketBase pbInstance)
@ -100,7 +99,7 @@ public class PocketBase
*
* Returns: A list of type <code>RecordType</code>
*/
private RecordType[] listRecords_internal(RecordType)(string table, ulong page = 1, ulong perPage = 30, string filter = "", bool isAuthCollection = false)
private RecordType[] listRecords_internal(RecordType)(string table, ulong page, ulong perPage, string filter, bool isAuthCollection)
{
// Set authorization token if setup
HTTP httpSettings = HTTP();
@ -188,6 +187,10 @@ public class PocketBase
{
throw new PocketBaseParsingException();
}
catch(SerializationError e)
{
throw new RemoteFieldMissing();
}
}
/**
@ -230,7 +233,7 @@ public class PocketBase
*
* Returns: An instance of the created <code>RecordType</code>
*/
private RecordType createRecord_internal(string, RecordType)(string table, RecordType item, bool isAuthCollection = false)
private RecordType createRecord_internal(string, RecordType)(string table, RecordType item, bool isAuthCollection)
{
idAbleCheck(item);
@ -301,6 +304,10 @@ public class PocketBase
{
throw new PocketBaseParsingException();
}
catch(SerializationError e)
{
throw new RemoteFieldMissing();
}
}
/**
@ -348,7 +355,6 @@ public class PocketBase
recordResponse["email"] = "";
}
recordOut = fromJSON!(RecordType)(recordResponse);
// Store the token
@ -377,6 +383,10 @@ public class PocketBase
{
throw new PocketBaseParsingException();
}
catch(SerializationError e)
{
throw new RemoteFieldMissing();
}
}
/**
@ -472,11 +482,30 @@ public class PocketBase
{
throw new PocketBaseParsingException();
}
catch(SerializationError e)
{
throw new RemoteFieldMissing();
}
}
/**
* Updates the given record in the given table, returning the
* updated record
* updated record (auth collections)
*
* Params:
* table = tabe table to update the record in
* item = the record of type <code>RecordType</code> to update
*
* Returns: The updated <code>RecordType</code>
*/
public RecordType updateRecordAuth(string, RecordType)(string table, RecordType item)
{
return updateRecord_internal(table, item, true);
}
/**
* Updates the given record in the given table, returning the
* updated record (base collections)
*
* Params:
* table = tabe table to update the record in
@ -485,6 +514,23 @@ public class PocketBase
* Returns: The updated <code>RecordType</code>
*/
public RecordType updateRecord(string, RecordType)(string table, RecordType item)
{
return updateRecord_internal(table, item, false);
}
/**
* Updates the given record in the given table, returning the
* updated record (internal)
*
* Params:
* table = tabe table to update the record in
* item = the record of type <code>RecordType</code> to update
* isAuthCollection = true if this is an auth collection, false
* for base collection
*
* Returns: The updated <code>RecordType</code>
*/
private RecordType updateRecord_internal(string, RecordType)(string table, RecordType item, bool isAuthCollection)
{
idAbleCheck(item);
@ -506,6 +552,21 @@ public class PocketBase
string responseData = cast(string)patch(pocketBaseURL~"collections/"~table~"/records/"~item.id, serialized.toString(), httpSettings);
JSONValue responseJSON = parseJSON(responseData);
// If this is an authable record (meaning it has email, password and passwordConfirm)
// well then the latter two will not be returned so fill them in. Secondly, the email
// will only be returned if `emailVisibility` is true.
if(isAuthCollection)
{
responseJSON["password"] = "";
responseJSON["passwordConfirm"] = "";
// If email is invisible make a fake field to prevent crash
if(!responseJSON["emailVisibility"].boolean())
{
responseJSON["email"] = "";
}
}
recordOut = fromJSON!(RecordType)(responseJSON);
return recordOut;
@ -538,6 +599,10 @@ public class PocketBase
{
throw new PocketBaseParsingException();
}
catch(SerializationError e)
{
throw new RemoteFieldMissing();
}
}
/**
@ -589,7 +654,7 @@ public class PocketBase
deleteRecord(table, record.id);
}
mixin template MemberAndType(alias record, alias typeEnforce, string memberName)
private mixin template MemberAndType(alias record, alias typeEnforce, string memberName)
{
static if(__traits(hasMember, record, memberName))
{
@ -792,6 +857,12 @@ unittest
// assert(cmp(people[0].email, p1.email) == 0);
string newName = "Bababooey";
person.name = newName;
person = pb.updateRecordAuth("dummy_auth", person);
assert(cmp(person.name, newName) == 0);
string tokenIn;
Person authPerson = pb.authWithPassword!(Person)("dummy_auth", p1.username, passwordToUse, tokenIn);
@ -801,9 +872,9 @@ unittest
writeln("Token: "~tokenIn);
// Ensure we get our person back
assert(cmp(authPerson.name, p1.name) == 0);
assert(authPerson.age == p1.age);
assert(cmp(authPerson.email, p1.email) == 0);
assert(cmp(authPerson.name, person.name) == 0);
assert(authPerson.age == person.age);
assert(cmp(authPerson.email, person.email) == 0);
// Delete the record
pb.deleteRecord("dummy_auth", p1);

View File

@ -61,3 +61,12 @@ public final class PocketBaseParsingException : PBException
{
}
public final class RemoteFieldMissing : PBException
{
this()
{
}
}

View File

@ -1,4 +1,4 @@
module libpb;
public import libpb.exceptions;
public import libpb.driver;
public import libpb.driver;

View File

@ -1,109 +0,0 @@
module libpb.serialization;
import std.json;
import std.conv : to;
import std.traits : FieldTypeTuple, FieldNameTuple;
public JSONValue serializeRecord(RecordType)(RecordType record)
{
// Final JSON to submit
JSONValue builtJSON;
// Alias as to only expand later when used in compile-time
alias structTypes = FieldTypeTuple!(RecordType);
alias structNames = FieldNameTuple!(RecordType);
alias structValues = record.tupleof;
static foreach(cnt; 0..structTypes.length)
{
debug(dbg)
{
pragma(msg, structTypes[cnt]);
pragma(msg, structNames[cnt]);
// pragma(msg, structValues[cnt]);
}
static if(__traits(isSame, mixin(structTypes[cnt]), int))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(__traits(isSame, mixin(structTypes[cnt]), uint))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(__traits(isSame, mixin(structTypes[cnt]), ulong))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(__traits(isSame, mixin(structTypes[cnt]), long))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(__traits(isSame, mixin(structTypes[cnt]), string))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(__traits(isSame, mixin(structTypes[cnt]), JSONValue))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(__traits(isSame, mixin(structTypes[cnt]), bool))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else
{
debug(dbg)
{
pragma(msg, "Yaa");
}
builtJSON[structNames[cnt]] = to!(string)(structValues[cnt]);
}
}
return builtJSON;
}
// Test serialization of a struct to JSON
private enum EnumType
{
DOG,
CAT
}
unittest
{
import std.algorithm.searching : canFind;
import std.string : cmp;
import std.stdio : writeln;
struct Person
{
public string firstname, lastname;
public int age;
public string[] list;
public JSONValue extraJSON;
public EnumType eType;
}
Person p1;
p1.firstname = "Tristan";
p1.lastname = "Kildaire";
p1.age = 23;
p1.list = ["1", "2", "3"];
p1.extraJSON = parseJSON(`{"item":1, "items":[1,2,3]}`);
p1.eType = EnumType.CAT;
JSONValue serialized = serializeRecord(p1);
string[] keys = serialized.object().keys();
assert(canFind(keys, "firstname") && cmp(serialized["firstname"].str(), "Tristan") == 0);
assert(canFind(keys, "lastname") && cmp(serialized["lastname"].str(), "Kildaire") == 0);
assert(canFind(keys, "age") && serialized["age"].integer() == 23);
debug(dbg)
{
writeln(serialized.toPrettyString());
}
}