FirelyTeam / firely-net-sdk

The official Firely .NET SDK for HL7 FHIR
Other
829 stars 345 forks source link

Design and extend "IScopedNode" #2894

Open ewoutkramer opened 1 month ago

ewoutkramer commented 1 month ago

We need a new interface with at least Parent (and maybe Resolve()). This is the "ITypedElement" for POCOs. See #2893 for more discussion on the design need and issues for this interface.

ewoutkramer commented 1 month ago

Kas and I decided to call it "IScopedNode" for now, since it is still basically ITypedElement + more stuff - Definition.

ewoutkramer commented 1 week ago

Final design:

// Now.
IDictionary<string,object>

object:

 * "value" => int,string,boolean,....
 * "id" / "url" => string
 * List<>
 * Base

// ITypedElement
* !"value"   (NB Extension.value)
* "id" / "url"  => FhirString, FhirUri (*) POCO: string, string
* ITypedElement****
* Value (CQL types)

Remarks:
* Het feit dat Element.ElementId een string is en Extension.Url een string geeft veel uitzonderingen in de code, want je moet het vaak als Base behandelen.
* Graag gebruiken wij de key "value" niet, want dit is toch een speciaal geval, heeft geen zin om hetzelfde te behandelen als de andere properties.

// Then
Base:

partial class Patient
{
    // Slimme getters en setters.
    public FhirBoolean Active { get; set; }

    Base[] Children(string? name)
    {
     //   Base[].Parent = this;
    }

    bool TryGetElement(string, Base[])
    {

    }

    void SetElement(string, Base[])

    string GetLocation();
    Base Resolve()
}

public Base? Child(this Base?  b, string name)
{
    if(b is null) return null;
    return b.TryGetValue(name, bla) ? bla.SingleOrDefault() : null;
}

public T? Child<T>(this Base?, string name) => ...

var p = new Patient();

p.Children("active").Resolve();
p.Child<FhirBoolean>("active").Value = true;

p.Active.Resolve();

// Tweede oplossing, zonder slimme setters/getters, uses Navigate() to step to other "world" of navigation.
// Onze oude PocoElementNode
record ScopedNode(Base[] Poco) : ITypedElement
{
    private Name;
    private Parent;
    private Index

    // Hieronder de navigatie
    IEnumerable<ScopedNode> Children()
    this[]
    SetValue()

    Resolve()
    GetLocation()

    IEnumerable<ITypedElement> ITypedElement.Children(string name) =>
        this.Children(name);

    bool Equals(object other) => other == this;

    public implicit operator Base(ScopedNode sn) => sn.Poco.SingleOrDefault();
    public explicit operator Base[](ScopedNode sn) => sn.Poco;
}

var x = p.Navigate()["active"] as FhirBoolean;
x.Value = false;

ITypedElement ToTypedELement(this Base b) => b.Navigate();

var p =;

// Add dynamic stuff
dynamic pn = p;
var d = pn.name.given;
Kasdejong commented 1 week ago
record ScopedNode<T>(T[] Poco) : ITypedElement where T : Base
{
    private Name;
    private Parent;
    private Index;

    public T? Single => Poco.SingleOrDefault();

    IEnumerable<ScopedNode<Base>> Children() => // some impl with GetElemPairs on the poco.
    ScopedNode<Base>? Child(string name) => // some impl with TryGetValue on the poco.
    this[]
    SetValue()

    Resolve()
    GetLocation() // remark: the Location property is part of ITE, so we cant make this a method.

    IEnumerable<ITypedElement> ITypedElement.Children(string name) =>
        this.Children(name);

    bool Equals(object other) => other == this;

    public implicit operator Base(ScopedNode sn) => sn.Poco.SingleOrDefault();
    public explicit operator Base[](ScopedNode sn) => sn.Poco;
}

static class ScopedNodeExtensions
{
    public static IEnumerable<ScopedNode<T>> Children<T>(this ScopedNode<Base>? sn, string? name = null) where T : Base 
        => name is null
            ? sn?.Children().OfType<T>() ?? []
            : sn?.Children(name).OfType<T>() ?? [];

    public static ScopedNode<T>? Child<T>(this ScopedNode? sn, string name) where T : Base 
        => sn.Child() is {Single is T} node 
            ? node 
            : null;
}

var pat = new Patient();
ScopedNode<HumanName> x = pat.Navigate().Child<HumanName>("name"); // null if multiple or none.
ScopedNode<HumanName> y = pat.Navigate().Children<HumanName>("name"); // empty if none.

HumanName z = pat.Navigate().Child<HumanName>("name").Single; // poco
string s = pat.Navigate().Child("name").Child<FhirString>("given").Value;

// or maybe even:
string s = pat.Navigate().Child<FhirString>("name.given").Value; // Children would have to allow period-delimited names.
``