Open kirkbrauer opened 1 year ago
Looks good. I'd originally just put in placeholders for the models. Breaking out a library for this could be good.
This does bring up a question. If we use these internally, do we use project references internally and separately pack a nuget library for other projects to use if they want?
@benrick I think the way I've seen it done with monorepos is for us to reference the library as a project reference and then publish a version that is synchronized across the project. So if we publish multiple NuGet packages, they all get published as version 1.0.0.
Yep, that's exactly what I was thinking we'd do is the monorepo like that, rather than breaking into separate ones, which would just overcomplicate changes to the references packages.
I took a stab at implementing this, I'll make a draft PR when I can. There are a lot of decisions to make with regards to how we're going to serialize/deserialize the objects. The schema is kinda complex and is backed by JSON-LD. I know there is a library for that, but it's Newtonsoft JSON and doesn't directly serialize into .NET types, it appears to just operate on JObjects. The good news is that there appears to be type discriminators in many places, so we can determine which type of object to deserialize with the new System.Text.Json
features in .NET 7.
I have looked more into the specification, and I found that the vast majority of the ActivityPub functionality is derived from the ActivityStreams spec. In fact, it appears that ActivityPub is a superset of ActivityStreams and both are supersets of JSON-LD documents.
I think implementing a Smilodon.ActivityStreams
library first might be the easiest thing to do since it would provide a good foundation for all the other modules to be build on top of like Smilodon.ActivityPub
. It appears that we don't have to worry about JSON-LD (other than the @context
property) because both specs prefer the human-readable JSON representations of the document.
However, this does lead to a few decisions that need to be made with regards to how we're going to serialize/deserialize ActivityStreams and later ActivityPub JSON objects. It appears that there are two core types Object
and Link
, which form a disjoint relationship. I think the best way to represent this would be to have an interface called IObjectOrLink
, which is implemented by both and has all common properties (e.g. @context
, type
, id
, etc.). There are a few more instances like this where we could use a disjoint interface to represent the difference. In C#, pattern matching will allow us to unwrap this after deserialization. When serializing, we can use the type
property as a type discriminator to allow us to determine which object type we are dealing with.
The other question is how we should deal with Link
s to other objects. In the spec, there appears to be both a long and short form (either using a full Link
object or just a string URI. Even though the JSON may serialize the Link
as a URI string, I think when deserializing, we should always produce the most verbose and explicit representation, so all URIs become Link
objects. This would make parsing an ActivityPub object super easy as it will always be in the exact same format on our end.
When serializing back to JSON, we can maybe use custom JsonConverter<T>
s to automatically decide which form to use based on some sort of config and whether the Link
has properties besides href
, @context
, and type
. For developer experience, this can be eased by maybe providing a select number of implicit cast operators that will allow you to type a Uri
or string
directly and have it automatically wrapped in a Link
object.
We also need to decide which .NET types we should be mapping some of the JSON values to. It appears that there are a lot if xsd:anyURI
s in the spec, which could be either a Uri
or string
in our representation. There are also a few special types like xsd:duration
, which could be mapped to a TimeSpan
or a string
. The most interesting type is xsd:string | rdf:langString
, which specifies an optionally i18n string. This might be a good use case for a custom LangString
class, which has constructors for both a single string
and IDictionary<string, string>
as well as implicit casts from string
to make it easier for developers to write. This would also probably need methods to fetch the text for the invariant culture, or a specific language (if available).
Is your feature request related to a problem? Please describe. We should use this issue as a place to start planning the implementation of the ActivityPub library. I just finished reading the spec, it appears that we'll need some interesting data models and maybe some special JSON parsing setup to handle the multiple types of requests/responses specified by ActivityPub. We will also need extension methods to support configuring this with minimal APIs and the WebApplicationBuilder.
Describe the solution you'd like I think the first step to implementing this will be to move everything into a project named
ActivityPub
with a structure similar to the following:Describe alternatives you've considered Another option would be to create separate projects for the ActivityPub models and the AspNetCore configuration. Right now, we just need the models, so maybe start out with that project and decide how to evolve in the future.