tinystruct Development Patterns
Credit
This skill comes from ECC. Credit ECC in any copy or changed version.
Use This Skill When
Use this skill when you:
- Create a module that extends
AbstractApplication. - Add a CLI or HTTP action with
@Action. - Read request data from
Context. - Return JSON data with
Builder. - Change
application.properties. - Create or rebuild
bin/dispatcher. - Fix route clashes or CLI argument errors.
Main Rules
- Extend
AbstractApplication. - Put
@Actiononly onpublicmethods. - Use one action pattern for both CLI and HTTP when they do the same job.
- Set the HTTP mode when an action must use a set method, such as POST.
- Use
Builderfor JSON data. - Use
Contextonly for data tied to the current request. - Turn off views for an API-only app.
- Use
bin/dispatcheras the main CLI entry point. - Let tinystruct find actions. Do not add them to
ActionRegistryby hand. - Test both valid and bad input.
Build Steps
1. Create the app
public class MyService extends AbstractApplication {
@Override
public void init() {
setTemplateRequired(false);
}
@Override
public String version() {
return "1.0.0";
}
}Call setTemplateRequired(false) when the app returns data and has no .view files.
2. Add a simple action
@Action("greet")
public String greet() {
return "Hello from tinystruct!";
}Run it from the CLI:
bin/dispatcher greetThe same action can be used as an HTTP route when the app runs as a web service.
3. Add route values
Each regex capture group must match one method argument. Keep the same order.
@Action("api/user/(\\d+)")
public String getUser(int userId) {
return "User ID: " + userId;
}Examples:
HTTP: /api/user/123
CLI: bin/dispatcher api/user/123Use a narrow pattern. For an ID, prefer (\\d+) over (.*).
4. Set the HTTP method
Use an HTTP mode when a route must accept only one method.
@Action(value = "login", mode = Mode.HTTP_POST)
public boolean login() {
return true;
}Do not put secret data in a URL. Read passwords and tokens from the request body or request context.
5. Return JSON data
Use tinystruct's Builder. Do not add Gson or Jackson just for basic JSON.
@Action("api/data")
public Builder getData() throws ApplicationException {
Builder result = new Builder();
result.put("status", "success");
Builder user = new Builder();
user.put("id", 1);
user.put("name", "James");
result.put("data", user);
return result;
}Keep one clear shape for both success and error data. Do not return a Builder on success and a plain string on failure.
6. Use request context with care
Use Context for request values, session values, and other request state.
- Check that a value exists before using it.
- Check its type before casting it.
- Do not store request state in static fields.
- Do not reuse one request's data in another request.
- Handle missing or bad values with a clear error result.
See System and Usage for the exact Context calls.
7. Set app options
Put settings in:
src/main/resources/application.propertiesUse the exact property names listed in Architecture and Settings.
Do not commit passwords, keys, or tokens. Read them from the safe setting source used by the project.
8. Create the dispatcher
Use ApplicationManager.init() when the project needs a new or rebuilt bin/dispatcher.
Before replacing an existing script:
- Check whether it has local changes.
- Keep its run rights.
- Run one known action after it is built.
Do not add a separate main(String[] args) to each module.
Full Usage Example
This service supports one route in both CLI and HTTP modes.
public class UserService extends AbstractApplication {
@Override
public void init() {
setTemplateRequired(false);
}
@Override
public String version() {
return "1.0.0";
}
@Action("api/user/(\\d+)")
public Builder getUser(int userId) throws ApplicationException {
Builder result = new Builder();
result.put("status", "success");
Builder user = new Builder();
user.put("id", userId);
user.put("name", "James");
result.put("data", user);
return result;
}
}Run it:
bin/dispatcher api/user/123Test these cases:
api/user/123 should match
api/user/0 should match if zero is allowed
api/user/abc should not match
api/user/ should not matchAlso call the HTTP route and check that it returns the same data shape.
Edge Cases
Route clashes
Two actions may match the same path.
- Search all
@Actionvalues. - Make each pattern as narrow as possible.
- Check fixed routes before adding broad regex routes.
- Test the path in both CLI and HTTP modes.
Route value errors
A route may match text that the method cannot read.
- Match digits for an
int. - Keep capture groups and method arguments in the same order.
- Do not add a capture group unless the method accepts it.
- Test very large numbers and missing values.
Wrong HTTP method
A route may exist but reject the request method.
- Check the action's
mode. - Use
Mode.HTTP_POSTfor POST-only work. - Do not let a state-changing action run through GET unless the app calls for it.
Missing views
If an API-only app throws FileNotFoundException for a .view file, call:
setTemplateRequired(false);Do this in init().
Bad or missing settings
- Give clear errors for required settings.
- Do not hide a missing setting with an unsafe default.
- Keep local and test settings apart when the project supports it.
- Do not print secret values in logs or errors.
JSON values
- Pick one rule for missing values, such as
nullor no field. - Keep number, text, and boolean types stable.
- Use nested
Buildervalues for nested objects. - Check Data Handling before adding lists or custom types.
Tests
Use JUnit 5.
Test that:
- The app starts.
- Each action is in
ActionRegistry. - Valid paths reach the right action.
- Bad paths do not match.
- Capture groups map to the right argument types.
- HTTP method limits work.
- CLI and HTTP calls give the same result shape.
- API-only apps do not look for view files.
- Missing context and settings fail in a clear way.
Do not rely only on direct method tests. A direct call will not catch a bad @Action pattern.
Common Problems
| Problem | Fix |
|---|---|
| Gson or Jackson was added for basic JSON | Use org.tinystruct.data.component.Builder. |
A .view file is missing |
Call setTemplateRequired(false) in init(). |
| An action is not found | Make the method public and check its @Action value. |
A route value cannot become an int |
Use a digit-only capture group and test its range. |
| Two actions match one path | Make the patterns more exact. |
| CLI and HTTP give different results | Put shared work in one method and keep input rules the same. |
Each module has its own main method |
Use bin/dispatcher. |
Code adds actions to ActionRegistry by hand |
Use @Action and automatic discovery. |
| Request data is stored in a static field | Keep it in Context or a local value. |
Reference Files
Read only the file needed for the task:
- Architecture and Settings: app base classes, packages, and properties.
- Routing and @Action: route patterns, modes, and arguments.
- Data Handling:
Builder, nested data, and JSON values. - System and Usage:
Context, sessions, events, and CLI use. - Testing: JUnit 5 and
ActionRegistrytests.