Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
runs-on: ${{ matrix.os }}
permissions:
contents: read
id-token: write
strategy:
fail-fast: false
matrix:
Expand All @@ -24,12 +27,12 @@ jobs:
version: '1'
arch: x86
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: actions/cache@v1
- uses: actions/cache@v4
env:
cache-name: cache-artifacts
with:
Expand All @@ -42,16 +45,24 @@ jobs:
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v5
with:
file: lcov.info
files: lcov.info
# Repository jobs authenticate with GitHub OIDC; public fork PRs upload without a token.
use_oidc: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
docs:
name: Documentation
runs-on: ubuntu-latest
permissions:
contents: write
statuses: write
steps:
- uses: actions/checkout@v2
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-docdeploy@latest
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: '1'
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-docdeploy@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
4 changes: 4 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"

[compat]
Documenter = "1"
3 changes: 1 addition & 2 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ makedocs(;
pages=[
"Home" => "index.md",
],
repo="https://github.com/JuliaData/StructTypes.jl/blob/{commit}{path}#L{line}",
repo=Documenter.Remotes.GitHub("JuliaData", "StructTypes.jl"),
sitename="StructTypes.jl",
authors="Jacob Quinn",
assets=String[],
)

deploydocs(;
Expand Down
45 changes: 34 additions & 11 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Depth = 3

## `StructTypes.StructType`

```@docs
StructTypes.StructType
```

In general, custom Julia types tend to be one of: 1) "data types", 2) "interface types" or sometimes 3) "custom types" or 4) "abstract types" with a known set of concrete subtypes. Data types tend to be "collection of fields" kind of types; fields are generally public and directly accessible, they might also be made to model "objects" in the object-oriented sense. In any case, the type is "nominal" in the sense that it's "made up" of the fields it has, sometimes even if just for making it more convenient to pass them around together in functions.

Interface types, on the other hand, are characterized by **private** fields; they contain optimized representations "under the hood" to provide various features/functionality and are useful via interface methods implemented: iteration, `getindex`, accessor methods, etc. Many package-provided libraries or Base-provided structures are like this: `Dict`, `Array`, `Socket`, etc. For these types, their underlying fields are mostly cryptic and provide little value to users directly, and are often explictly documented as being implementation details and not to be relied upon directly under warning of breakage.
Expand All @@ -33,6 +37,10 @@ Each of these 4 kinds of struct type categories will be now be detailed.

### DataTypes

```@docs
StructTypes.DataType
```

You'll remember that "data types" are Julia structs that are "made up" of their fields. In the object-oriented world,
this would be characterized by marking a field as `public`. A quick example is:

Expand Down Expand Up @@ -74,13 +82,18 @@ Support functions for `StructTypes.DataType`s:
StructTypes.names
StructTypes.excludes
StructTypes.omitempties
StructTypes.defaults
StructTypes.keywordargs
StructTypes.idproperty
StructTypes.fieldprefix
```

### Interface Types

```@docs
StructTypes.InterfaceType
```

For interface types, we don't want the internal fields of a type exposed, so an alternative API is to define the closest "basic" type that our custom type should map to. This is done by choosing one of the following definitions:
```julia
StructTypes.StructType(::Type{MyType}) = StructTypes.DictType()
Expand Down Expand Up @@ -129,6 +142,12 @@ StructTypes.BoolType
StructTypes.NullType
```

#### `StructTypes.NoStructType`

```@docs
StructTypes.NoStructType
```

### CustomStruct

```@docs
Expand Down Expand Up @@ -165,14 +184,18 @@ The `StructType` of a type can be set using the following utility macros
StructTypes.@Struct
```

And similarly for
- `StructTypes.@Mutable`
- `StructTypes.@CustomStruct`
- `StructTypes.@OrderedStruct`
- `StructTypes.@AbstractType`
- `StructTypes.@DictType`
- `StructTypes.@ArrayType`
- `StructTypes.@StringType`
- `StructTypes.@NumberType`
- `StructTypes.@BoolType`
- `StructTypes.@NullType`
The corresponding macros are available for the other struct types:

```@docs
StructTypes.@Mutable
StructTypes.@CustomStruct
StructTypes.@OrderedStruct
StructTypes.@AbstractType
StructTypes.@DictType
StructTypes.@ArrayType
StructTypes.@StringType
StructTypes.@NumberType
StructTypes.@BoolType
StructTypes.@NullType
StructTypes.@register_struct_subtype
```
11 changes: 4 additions & 7 deletions src/StructTypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct CustomStruct <: StructType end
This is a required method for types declaring `StructTypes.CustomStruct`.
Allows objects of type `T` to conveniently serialize/deserialize as another type, when their own
structure/definition isn't significant. Useful for wrapper types. See also
[`StructTypes.CustomStruct`](@ref) and [`StructType.lowertype`](@ref).
[`StructTypes.CustomStruct`](@ref) and [`StructTypes.lowertype`](@ref).
"""
function lower end

Expand Down Expand Up @@ -218,7 +218,7 @@ isempty(::Type{T}, x) where {T} = isempty(x) # generic fallback
isempty(::Type{T}, x, i) where {T} = isempty(T, Core.getfield(x, i)) # generic fallback

"""
StructTypes.defaults(::Type{MyType}) = (:field_a=default_value, :field_b=>default_value)
StructTypes.defaults(::Type{MyType}) = (field_a=default_value, field_b=default_value)

Define default arguments for various fields of `MyType`, which will be used to initialize the name-value
dictionary used in `StructTypes.construct`.
Expand Down Expand Up @@ -847,11 +847,11 @@ end
"""
StructTypes.applyfield(f, ::Type{T}, nm::Symbol) => Bool

Convenience function for working with a `StructTypes.Mutable` object. For a given serialization name `nm`,
Convenience function for working with the fields of type `T`. For a given serialization name `nm`,
apply the function `f(i, name, FT; kw...)` to the field index `i`, field name `name`, field type `FT`, and
any keyword arguments `kw` defined in `StructTypes.keywordargs`.
Various StructType configurations are respected like keyword arguments, names, and exclusions.
`applyfield` returns whether `f` was executed or not; if `nm` isn't a valid field name on `x`, `false`
`applyfield` returns whether `f` was executed or not; if `nm` isn't a valid field name on `T`, `false`
will be returned (important for applications where the input still needs to consume the field, like json parsing).
Note that the input `nm` is treated as the serialization name, so any `StructTypes.names`
mappings will be applied, and the function will be passed the Julia field name.
Expand Down Expand Up @@ -890,9 +890,6 @@ mappings will be applied, and the function will be passed the Julia field name.
end
end
)
if !f_applied && haskey(defaults(T), nm)
setfield!(x, nm, defaults(T)[nm])
end
return f_applied
end

Expand Down
17 changes: 17 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ builtin_type_mapping = Dict(
Base.Cwstring,
Base.Regex,
Base.VersionNumber,
UUID,
},
StructTypes.NumberType() => Union{
Core.Number, # Should all of `Number` and its subtypes be Numbers by default?
Expand Down Expand Up @@ -825,6 +826,22 @@ StructTypes.defaults(::Type{Defaultable}) = (b="b",)
@test StructTypes.constructfrom(Defaultable, (a="a", b="c")) == Defaultable("a", "c")
end

struct DefaultedFields
value::Int
end
StructTypes.StructType(::Type{DefaultedFields}) = StructTypes.Struct()
StructTypes.defaults(::Type{DefaultedFields}) = (value=10, extra=20)

@testset "applyfield with defaults for unknown names" begin
calls = Ref(0)
callback = (i, nm, FT) -> (calls[] += 1; 99)
@test StructTypes.applyfield(callback, DefaultedFields, :extra) === false
@test calls[] == 0
@test StructTypes.applyfield(callback, DefaultedFields, :value) === true
@test calls[] == 1
@test StructTypes.constructfrom(DefaultedFields, Dict{Symbol, Int}()).value == 10
end

# CustomStruct subtypes
abstract type Vehicle2 end

Expand Down
Loading