Skip to content

Package Format

mafried edited this page Nov 22, 2023 · 18 revisions

Packages are used to describe available types (and nodes which are types as well) and reflect exactly the corresponding Rust packages (without describing all types in the Rust package). Types are described together with possible type arguments and constructors. The code generator that transforms flow project json files into Rust code uses this information.

Fields

The package format uses JSON syntax and contains the following elements:

Package:

  • name: The package name (type: String).
  • version: The package version (type: String, format: semantic versioning).
  • crates: The crates defined in this package. Crates reflect the structure of corresponding Rust crates (type: Dictionary of Crate objects (key: crate name, value: Crate object)).

Crate:

  • types: Types defined in this crate (type: Dictionary of Type objects (key: type name, value: Type object)).
  • modules: Modules defined in this crate. Modules reflect the structure of corresponding module crates (type: Dictionary of Module objects (key: module name, value: Module object)).

Module:

  • types: Types defined in this module (type: Dictionary of Type objects (key: type name, value: Type object)).
  • modules: Modules defined in this module (type: Dictionary of Module objects (key: module name, value: Module object)).

Type:

  • inputs: Optional list of input edges. If the type implements the Node trait, this needs to be set (type: Dictionary of TypeDescription objects (key: input name, value: TypeDescription object)).
  • outputs: Optional list of output edges. If the type implements the Node trait, this needs to be set (type: Dictionary of TypeDescription objects (key: output name, value: TypeDescription object)).
  • type_parameters: Optional list of type parameters in case the type is generic (type: List of Strings).
  • constructors: Constructors that can be used to create an instance of this type (type: Dictionary (key: constructor name, value: Constructor object)).

Constructor:

Reflects the flowrs_build::package::Constructor enumeration. Describes in detail how code for the constructor function should be created. The necessary fields depend on the enumeration type. Currently, the following types exist:

New

A simple constructor function without any parameters.

  • function_name: The optional name of the constructor function. Default: "new" (type: String).

NewWithObeserver

A simple constructor function with a single parameter being the globally available ChangeObserver object.

  • function_name: The optional name of the constructor function. Default: "new" (type: String).

Example:

"DebugNode":{
  ...
   "constructors":{
      "New":{
         "NewWithObserver":{
         
         }
      }
   }
}

NewWithObserverAndContext

A simple constructor function with two parameters: The globally available ChangeObserver and Context objects.

  • function_name: The optional name of the constructor function. Default: "new" (type: String).

NewWithArbitraryArgs

A constructor with arbitrary arguments.

  • function_name: The optional name of the constructor function. Default: "new" (type: String).
  • arguments: The arguments of the constructor function (type: List of Argument objects).

Argument:

  • name: The name of the argument (type: String).
  • type: The argument type (type: TypeDescription)
  • passing: Describes how the argument is passed to the constructor function (type: ArgumentPassing)
  • construction: Describes how the argument is constructed (type: ArgumentConstruction)

ArgumentPassing: Enumeration

  • Move
  • Clone
  • MutableReference
  • Reference

ArgumentConstruction: Enumeration

  • ExistingObject: One of the global objects (change_observer, context).
  • Constructor: Name of the constructor to use for argument construction (type: String).

TypeDescription: Enumeration

  • Type: A specific type (that can have additional type parameters).
    • name: Name of the type (type: String).
    • type_parameters: Optional list of type parameters (type: List of ArgumentType objects).
  • Generic: A generic type (that can have additional type parameters).
    • name: Name of the type (type: String).
    • type_parameters: Optional list of type parameters (type: List of ArgumentType objects).

Example:

{
   "ValueNode":{
      "outputs":{
         "output":{
            "type":{
               "Generic":{
                  "name":"I"
               }
            }
         }
      },
      "type_parameters":[
         "I"
      ],
      "constructors":{
         "New":{
            "NewWithArbitraryArgs":{
               "arguments":[
                  {
                     "type":{
                        "Generic":{
                           "name":"I"
                        }
                     },
                     "name":"value",
                     "passing":"Move",
                     "construction":{
                        "Constructor":"Json"
                     }
                  },
                  {
                     "type":{
                        "Type":{
                           "name":"()"
                        }
                     },
                     "name":"change_observer",
                     "passing":"Clone",
                     "construction":{
                        "ExistingObject":[
                           
                        ]
                     }
                  }
               ]
            }
         }
      }
   }
}

FromJson

A constructor that reads its arguments from JSON. The JSON is part of the flow project JSON.

Example:

"TimerNodeConfig":{
   "constructors":{
      "Json":"FromJson"
   }
}

FromDefault

A constructor that reads its arguments from JSON. The JSON is part of the flow project JSON.

Example:

"TimerNodeConfig":{
   "constructors":{
      "Default":"FromDefault"
   }
}

FromCode

A constructor that is directly written in Rust code.

  • code_template: The code that should be emitted for this constructor. The code can contain placeholders (marked with {{placeholder}}) that are replaced during code generation (type: String).

Possible placeholders:

  • fully_qualified_name: The fully qualified name of the object that should be created.
  • type_name: The name of the type.
  • type_parameter_part: The type parameter part of generic types.
  • type_parameter_NAME: The chosen type for type parameter NAME.
  • mutable: If the object to be created should be mutable, this field contains "mut". If not, it contains "".

Example:

"MyType":{
   "type_parameters":[
      "U",
      "T"
   ],
   "constructors":{
      "Code":{
         "FromCode":{
            "code_template":"let {{fully_qualified_name}}:{{type_parameter_U}} = 5;"
         }
      }
   }
}

Example

{
   "name":"flowrs-std",
   "version":"1.0.0",
   "crates":{
      "flowrs_std":{
         "types":{
            
         },
         "modules":{
            "nodes":{
               "types":{
                  
               },
               "modules":{
                  "debug":{
                     "modules":{
                        
                     },
                     "types":{
                        "DebugNode":{
                           "inputs":{
                              "input":{
                                 "type":{
                                    "Generic":{
                                       "name":"I"
                                    }
                                 }
                              }
                           },
                           "outputs":{
                              "output":{
                                 "type":{
                                    "Generic":{
                                       "name":"I"
                                    }
                                 }
                              }
                           },
                           "type_parameters":[
                              "I"
                           ],
                           "constructors":{
                              "New":{
                                 "NewWithObserver":{
                                    
                                 }
                              }
                           }
                        }
                     }
                  },
                  "value":{
                     "modules":{
                        
                     },
                     "types":{
                        "ValueType":{
                           "constructors":{
                              "Json":"FromJson"
                           }
                        },
                        "ValueNode":{
                           "outputs":{
                              "output":{
                                 "type":{
                                    "Generic":{
                                       "name":"I"
                                    }
                                 }
                              }
                           },
                           "type_parameters":[
                              "I"
                           ],
                           "constructors":{
                              "New":{
                                 "NewWithArbitraryArgs":{
                                    "arguments":[
                                       {
                                          "type":{
                                             "Generic":{
                                                "name":"I"
                                             }
                                          },
                                          "name":"value",
                                          "passing":"Move",
                                          "construction":{
                                             "Constructor":"Json"
                                          }
                                       },
                                       {
                                          "type":{
                                             "Type":{
                                                "name":"()"
                                             }
                                          },
                                          "name":"change_observer",
                                          "passing":"Clone",
                                          "construction":{
                                             "ExistingObject":[
                                                
                                             ]
                                          }
                                       }
                                    ]
                                 }
                              }
                           }
                        }
                     }
                  },
                  "timer":{
                     "modules":{
                        
                     },
                     "types":{
                        "TimerNodeConfig":{
                           "constructors":{
                              "Json":"FromJson"
                           }
                        },
                        "PollTimer":{
                           "type_parameters":[
                              "U"
                           ],
                           "constructors":{
                              "New":{
                                 "New":{
                                    
                                 }
                              }
                           }
                        },
                        "SelectedTimer":{
                           "type_parameters":[
                              "U"
                           ],
                           "constructors":{
                              "New":{
                                 "New":{
                                    
                                 }
                              }
                           }
                        },
                        "TimerNode":{
                           "inputs":{
                              "config_input":{
                                 "type":{
                                    "Type":{
                                       "name":"flowrs_std::nodes::timer::TimerNodeConfig"
                                    }
                                 }
                              },
                              "token_input":{
                                 "type":{
                                    "Generic":{
                                       "name":"U"
                                    }
                                 }
                              }
                           },
                           "outputs":{
                              "token_output":{
                                 "type":{
                                    "Generic":{
                                       "name":"U"
                                    }
                                 }
                              }
                           },
                           "type_parameters":[
                              "T",
                              "U"
                           ],
                           "constructors":{
                              "NewWithToken":{
                                 "NewWithArbitraryArgs":{
                                    "function_name":"new_with_token",
                                    "arguments":[
                                       {
                                          "type":{
                                             "Generic":{
                                                "name":"T",
                                                "type_parameters":[
                                                   {
                                                      "Generic":{
                                                         "name":"U"
                                                      }
                                                   }
                                                ]
                                             }
                                          },
                                          "name":"timer",
                                          "passing":"Move",
                                          "construction":{
                                             "Constructor":"New"
                                          }
                                       },
                                       {
                                          "type":{
                                             "Generic":{
                                                "name":"U"
                                             }
                                          },
                                          "name":"token_object",
                                          "passing":"Move",
                                          "construction":{
                                             "Constructor":"New"
                                          }
                                       },
                                       {
                                          "type":{
                                             "Type":{
                                                "name":"()"
                                             }
                                          },
                                          "name":"change_observer",
                                          "passing":"Clone",
                                          "construction":{
                                             "ExistingObject":[
                                                
                                             ]
                                          }
                                       }
                                    ]
                                 }
                              },
                              "New":{
                                 "NewWithArbitraryArgs":{
                                    "arguments":[
                                       {
                                          "type":{
                                             "Generic":{
                                                "name":"T",
                                                "type_parameters":[
                                                   {
                                                      "Generic":{
                                                         "name":"U"
                                                      }
                                                   }
                                                ]
                                             }
                                          },
                                          "name":"timer",
                                          "passing":"Move",
                                          "construction":{
                                             "Constructor":"New"
                                          }
                                       },
                                       {
                                          "type":{
                                             "Type":{
                                                "name":"()"
                                             }
                                          },
                                          "name":"change_observer",
                                          "passing":"Clone",
                                          "construction":{
                                             "ExistingObject":[
                                                
                                             ]
                                          }
                                       }
                                    ]
                                 }
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }
}

How-Tos

1. How-To: Create a new flow package

For creating a new flow package, add a new JSON file to the flow-packages folder (in florws-build/flow-packages). It should have the same name and version like the corresponding Rust package (e.g. flowrs-std.json for the flowrs-std package). Flow packages directly map to Rust packages and reflect their structure. That means e.g. that crates in the Rust package have their counterpart in the flow package JSON:

{
   "name":"flowrs-std",
   "version":"1.0.0",
   "crates":{
      "flowrs_std":{
        ...
   }
}

Like Rust crates, flow crates can have modules (which can contain modules) and types. See for example the type DebugNode:

{
   "name":"flowrs-std",
   "version":"1.0.0",
   "crates":{
      "flowrs_std":{
         "types":{
            
         },
         "modules":{
            "nodes":{
               "types":{
                  
               },
               "modules":{
                  "debug":{
                     "modules":{
                        "types":{
                           "DebugNode":{
                          
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }
}

It directly reflects the definition of the Rust struct DebugNode whose full Rust name is flowrs_std::nodes::debug::DebugNode (see also flowrs-std/nodes/debug.rs). BTW: Nodes are also types which contain additional input and output declarations. If a certain type should be instantiated within the initialization of a flow (like nodes used in the flow or constructor parameters of that nodes) it must be defined in a flow package.

2. How-To: Add a new simple type to a flow package

Let's say we want to add a new type named "ValueType" that should be instantiated using a JSON constructor. We choose a module in the flow package (based on the location of the corresponding Rust struct) and add our new type to its "types" dictionary:

 "ValueType":{
      "constructors":{
         "Json":"FromJson"
      }
   }

It has a constructor named "Json" that is of type "FromJson" which means that the type can be instantiated using JSON-encoded input data (which is defined in the flow project JSON's "data" field). If the type has just a simple "new" constructor without parameters, one would define a constructor of type "New":

 "ValueType":{
      "constructors":{
         "New":"New"
      }
   }

Node types often have constructors with a single argument, the change_observer. Defining this constructor is easy:

 "NodeType":{
      "constructors":{
         "New":{"NewWithObserver": {}},
      }
   }

If a node type has a constructor with an additional context parameter, another pre-defined constructor exists:

 "NodeType":{
      "constructors":{
         "New":{"NewWithObserverAndContext": {}},
      }
   }

If a type implements the default trait, a constructor of type "FromDefault" can be added.

3. How-To: Add a new node type to a flow package

Node types are types with inputs and outputs. A node's Rust struct might look like this:

#[derive(RuntimeConnectable)]
pub struct SimpleNode
{
    #[input]
    pub input: Input<i32>,
    #[output]
    pub output: Output<String>,
}

It has one input for items of type i32 and one output for items of type String. The corresponding JSON definition would look like this:

"SimpleNode":{
   "constructors":{
      
   },
   "inputs":{
      "input":{
         "type":{
            "Type":{
               "name":"i32"
            }
         }
      }
   },
   "outputs":{
      "output":{
         "type":{
            "Type":{
               "name":"String"
            }
         }
      }
   }
}

Currently, this node type does not have any constructor function, so let's add a simple constructor:

impl SimpleNode{
    pub fn new(change_observer: Option<&ChangeObserver>) -> Self {
        Self {
            output: Output::new(change_observer),
            input: Input::new()
        }
    }
}

On the JSON-side, we can specify now, that our type has this particular constructor (let's assume that this node type is defined in a package flowrs_simple within module nodes/simple):

"SimpleNode":{
   "constructors":{
      "New":{
         "NewWithObserver":{
            
         }
      }
   }
}

This constructor can then be referenced when defining a new node within the flow-project.json:

"flow":{
   "nodes":{
      "simple_node_1":{
         "node_type":"flowrs_simple::nodes::simple::SimpleNode",
         "constructor":"New"
      }     
   }
}

Now, let's reimplement this node for generic in- and outputs:

#[derive(RuntimeConnectable)]
pub struct SimpleNode<I, O> where I: Clone, O: Clone
{
    #[input]
    pub input: Input<I>,
    #[output]
    pub output: Output<O>,
}

And the corresponding JSON:

"SimpleNode":{
   "type_parameters":[
      "I",
      "O"
   ],
   "inputs":{
      "input":{
         "type":{
            "Generic":{
               "name":"I"
            }
         }
      }
   },
   "outputs":{
      "output":{
         "type":{
            "Generic":{
               "name":"O"
            }
         }
      }
   },
   "constructors":{     
   }
}

When do we specify the type parameters? This happens in the flow-project.json:

"flow":{
   "nodes":{
      "simple_node_1":{
         "node_type":"flowrs_simple::nodes::simple::SimpleNode",
         "constructor":"New",
         "type_parameters":{
            "I":"i32",
            "O":"String"
         }
      }
   }
}

4. How-To: Add a new node type with an arbitrary constructor

Let's modify the constructor of the node type SimpleNode a bit:

impl SimpleNode{
    pub fn new(v: i32, change_observer: Option<&ChangeObserver>) -> Self {
        Self {
            output: Output::new(change_observer),
            input: Input::new()
            value: value
        }
    }
}

For the JSON definition, there exists no pre-defined constructor that could be used, so we have to define it ourselves explicitly:

"SimpleNode":{
   ...
   "constructors":{
      "NewWithValue":{
         "NewWithArbitraryArgs":{
            "arguments":[
               {
                  "type":{
                     "Type":{
                        "name":"i32"
                     }
                  },
                  "name":"v",
                  "passing":"Move",
                  "construction":{
                     "Constructor":"Json"
                  }
               },
               {
                  "type":{
                     "Type":{
                        "name":"()"
                     }
                  },
                  "name":"change_observer",
                  "passing":"Clone",
                  "construction":{
                     "ExistingObject":[
                        
                     ]
                  }
               }
            ]
         }
      }
   }
}

The arguments field models the constructor function's arguments. The first argument "v" is of type i32 and is created using JSON deserialization (all built-in types like i32, i64 or char have a default constructor "Default" and a FromJson constructor "Json"). It is moved into the constructor function. The second argument "change_observer" uses an already existing object with the same name (ChangeObserver instantiation happens implicitly and before node instantiation. Thus, it does not have to be created explicitly. This is also the reason why we do specify the "()"-type by convention. We do not have to have a defined constructor). The argument is passed via cloning.

What if we want to use a generic type as argument type?

impl<I, O> SimpleNode<I, O>
where
    I: Clone, O: Clone
{
    pub fn new(v: std::vec::Vec<I>, change_observer: Option<&ChangeObserver>) -> Self {
        Self {
            v,
            input: Input::new(),
            output: Output::new(change_observer),
        }
    }
}

We have to adapt the JSON definition as follows:

"SimpleNode":{
   "constructors":{
      "NewWithValue":{
         "NewWithArbitraryArgs":{
            "arguments":[
               {
                  "type":{
                     "Type":{
                        "name":"std::vec::Vec",
                        "type_parameters":[
                           {
                              "Generic":{
                                 "name":"I"
                              }
                           }
                        ]
                     }
                  },
                  "name":"v",
                  "passing":"Move",
                  "construction":{
                     "Constructor":"Json"
                  }
               },
               {
                  "type":{
                     "Type":{
                        "name":"()"
                     }
                  },
                  "name":"change_observer",
                  "passing":"Clone",
                  "construction":{
                     "ExistingObject":[
                        
                     ]
                  }
               }
            ]
         }
      }
   }
}

Keep in mind, that, in order to make this work, you also need a JSON package "std" with the "Vec" type:

{
   "name":"std",
   "version":"1.0.0",
   "crates":{
      "std":{
         "types":{
            
         },
         "modules":{
            "vec":{
               "types":{
                  "Vec":{
                     "type_parameters":[
                        "I"
                     ],
                     "constructors":{
                        "Json":"FromJson"
                     }
                  }
               },
               "modules":{
                  
               }
            }
         }
      }
   }
}